QCon 演讲火热征集中,快来分享你的技术实践与洞见! 了解详情
写点什么

C++ 不是 C 的超集!

  • 2019-09-10
  • 本文字数:3846 字

    阅读完需:约 13 分钟

C++不是C的超集!

你可能听说过 C ++是 C 的超集。但如果你有两种编程语言的经验,你就会知道这根本不是真的。

当然,C ++有许多功能,C 没有;但也有一些功能只有 C 有,而 C++没有。 并且,也许最重要的是,有些代码可以在两种语言中编译,但却执行不同的操作。

你可以找到很多关于 C ++、C 之间异同的信息,但很多看起来很分散。在这里,我创建了一个简明的对比指南,并从 C、C++语言规范标准中摘录一些内容来支持这些异同。


注意事项:


本文主要针对 C、C++语言, 所以你需要熟悉 C 或 C ++中的其中之一,两个都熟悉则更好。


当我提到 C ++时,我指的是 C ++ 11 以上的版本,尽管本文大部分都适用于 C++早期的标准。 我也将引用 C ++ 17 标准 (目前 C++的最新标准)。


当我提到 C 时,我指的是 C99 标准,同时我也将参考 C11 标准(目前 C 的最新标准)。


值得注意的是,许多编译器不完全兼容编程语言标准。这正是难以确定什么是标准,什么是不合规以及什么是实现定义的部分原因。如果你想要查看其他编译器的示例,我建议使用Compiler Explorer亲自动手实践一番,对比很有趣。

同样的代码,用两种语言编译,但结果不同

我认为这是最重要的差异类别方法策略。


const


关键字 const 在 C ++中与在 C 中具有不同的语义,实际上,它比我在第一次撰写此博客文章时的想法更为微妙。


差异归结为编程语言是否允许常量表达的编写,常量表达式可以在编译器编译通过。例如,这里通过常量来界定静态数组的大小,下面的示例将用 C ++编译,但它是否在 C 中编译将是实现定义的:


1 const size_t buffer_size = 5;2 int buffer[buffer_size];3 4 // int main() {5     // ...6 // }

复制代码


但是常量表达式在 C 中的表现如何呢?


在这里,我们引用 C11 标准的几个部分以阐述为什么如此实现,C11 6.6 第 6 段定义了一个整数常量表达式:


整数常量表达式应具有整数类型,并且只能具有整数常量的操作数、枚举常量、字符常量,结果为整数常量的 sizeof 表达式,以及作为强制转换的直接操作数的浮点常量。 整数常量表达式中的转换运算符只能将算术类型转换为整数类型,除非作为 sizeof 运算符的操作数的一部分。


但什么是“整数常数”? 从 6.4.4 开始,这些是字面值,而不是变量,例如 1。


这归结为只有像 1 或 5 + 7 这样的表达式可以是 C 中的常量表达式。变量不能是常量表达式。 正如我所料,此示例在gcc编译编译不通过,但它确实可以在Clang编译通过:为什么?


答案见 C11 6.6 第 10 段:


一种实现可以接受其他形式的常量表达式。


所以在 C 中,如果要编写可移植版本代码,上面的代码必须使用宏预处理器:


1 #define BUFFER_SIZE (5)2 int buffer[BUFFER_SIZE];
复制代码


关键字 const 是由 Bjarne Stroustrop 为 C++创建的:减少对宏的需求。 所以,C ++对于什么是常量表达式更加宽容,使得 const 变量更强大。


我惊讶地发现 const 起源于 C ++,然后由 C 所采纳。我假设 const 来自 C,而 C ++采用相同的概念并扩展它以减少对宏的需求。我理解 C 语言对宏的广泛使用,但在标准化 C 时故意减少 const 的使用似乎并不明智。


修改 const 变量


以下代码在 C 中使用导致约束违规:


1 const int foo = 1;2 int* bar = &foo;3 *bar = 2;
复制代码


C11 6.5.16.1 第 1 段列出了一些约束说明,其中一个约束必须为真,类型转换才有效。我们的例子的相关约束如下:


左操作数具有原子性,限定或非限定指针类型,并且(考虑左值操作数在左值转换后将具有的类型)两个操作数都是指向兼容类型的限定或非限定版本的指针,左侧指向的类型具有全部右边指出的类型的限定符。


为了符合要求,如果存在约束违规,编译器必须进行诊断,这可能是警告或错误。 我发现它通常是一个警告,这意味着它通常可以在C中编译,但运行后会给出未定义的结果:


上述代码,在C ++中不会编译。 我认为这是因为 const T 是与 T 不同的类型,并且不允许隐式转换。 而在 C 中,const 只是一个限定符。


C ++ 17 6.7.3:


类型的 cv 限定或 cv 非限定版本是不同类型。


无参的函数声明


1 int func();
复制代码


在 C ++中,这声明了一个不带参数的函数。但同样的语法,在 C 中则声明了一个可以接受任意类型参数、任意数量参数的函数。


根据 C11 标准 6.7.6.3 第 10 和 14 段:


void 类型的未命名参数作为列表中唯一项的特殊情况指定该函数没有参数。

函数声明符中的空列表是该函数定义的一部分,指定该函数没有参数。函数声明符中的空列表不是该函数定义的一部分,它指定不提供有关参数数量或类型的信息。


所以在 C 中,以下代码将是合法的:


1 // func.h2 int func();
复制代码


1 // func.c2 int func(int foo, int bar) {3     return foo + bar;4 }
复制代码


1 // main.c2 #include "func.h"3 4 int main() {5     return func(5, 6);6 }
复制代码


不过,同样代码将导致 C ++中的编译器报错:


main.c:5:12: error: no matching function for call to ‘func’

return func(5, 6);

^~~~

./func.h:2:5: note: candidate function not viable:

requires 0 arguments, but 2 were provided


名称解析


有一些常见的实现细节,使我们可以进一步阐明这一点。 假如我在 Linux 机器上使用 Clang 编译器,则以下代码可以在 C 下编译和链接:


1 // func.h2 int func(int foo, int bar);
复制代码


1 #include <stdio.h>2 3 // func.c4 int func(float foo, float bar) {5     return printf("%f, %f\n", foo, bar);6 }
复制代码


1 // main.c2 #include "func.h"3 4 int main() {5     return func(5, 6);6 }
复制代码


但是上述代码却不能在 C ++中编译通过。


因为,C ++编译器通常使用名称来进行函数重载。它们“破坏”函数的名称以便对它们的参数进行编码,例如:通过将参数类型附加到函数中。通常,C 编译器只将函数名称存储为符号。我们可以通过反编译 C 和 C ++,来比较 func.o 的符号表看看这些区别。


C 编译的 func.o 解析如下:


╰─λ objdump -t func.o


func.o: file format elf64-x86-64


SYMBOL TABLE:


0000000000000000 l df ABS 0000000000000000 foo.c

0000000000000000 l d .text 0000000000000000 .text

0000000000000000 l d .rodata.str1.1 0000000000000000 .rodata.str1.1

0000000000000000 g F .text 000000000000002e func

0000000000000000 UND 0000000000000000 printf


C++编译的 func.o 解析如下:


╰─λ objdump -t func.o


func.o: file format elf64-x86-64


SYMBOL TABLE:


0000000000000000 l df ABS 0000000000000000 foo.c

0000000000000000 l d .text 0000000000000000 .text

0000000000000000 l d .rodata.str1.1 0000000000000000 .rodata.str1.1

0000000000000000 g F .text 000000000000003b _Z4funcff

0000000000000000 UND 0000000000000000 printf


auto


auto 在 C ++中用于类型自动推断,但同时 auto 也是一个 C 关键字,只是我从未真正看到工程实践的应用。


以下 C 具有约束违规,即未指定 type。这可能是错误,但我从来没有找到一个编译器给它任何东西,只是一个关于隐式转换的警告:


1 int main() {2     auto x = "actually an int";3     return x;4 }
复制代码


在 C99 之前,如果没有类型说明符是合法的,并且类型将被假定为 int。当我使用Clanggcc编译它时会发生这种情况,因此我们得到一个警告,因为隐式将 char 数组转换为 int。


在 C ++中,直接显示编译不通过,因为 x 的类型被推断为,


error: cannot initialize return object of type ‘int’ with an lvalue of type ‘const char *’

return x;

一些 C 有,但 C ++没有的功能

尽管 C 是一种非常短小精悍的编程语言,并且 C ++很庞大,但 C 语言中有一些 C ++没有的有用功能。


可变长度数组


VLA 允许定义具有可变长度的自动存储的数组。例如:


1 void f(int n) {2   int arr[n];3   // ......4 }
复制代码


实际上,VLA 在 C11 标准中是可选的,这使得它们无法移植。


但这些却不是 C ++的一部分,部分可能是因为 C ++标准库在很大程度上依赖于动态内存分配来创建使用 std::vector 类似的容器。


受限的指针


C 定义了第三种类型限定符(除了 const 和 volatile):restrict。这仅用于指针。使指针受限制告诉编译器“我将只通过此指针访问底层对象以获取此指针的范围”,因此它不能混淆。如果你打破这个约束,你将得到未定义的行为。


这有助于优化。一个典型的例子是 memmove,你可以告诉编译器 src 和 dst 不重叠。


引用 C11 6.7.3 第 8 段:


通过限制限定指针访问的对象与该指针具有特殊关联。这种关联在下面的 6.7.3.1 中定义,要求对该对象的所有访问都直接或间接地使用该特定指针的值.135)。

限制限定符(如寄存器存储类)的预期用途是促进优化,并从构成符合程序的所有预处理翻译单元中删除限定符不会改变其含义(即可观察行为)。


受限的指针不是 C ++标准的一部分,但实际上被许多编译器扩展支持。


我对受限的指针感到疑惑,因为它看起来好像玩火。有趣的是,在使用它时遇到编译器优化错误似乎很常见,因为我从未在真正使用过的代码中应用过它。


特定初始化程序


C99 引入了一种非常有用的初始化结构的方法,但我不明白它为什么没有被 C ++采用。


 1 typedef struct { 2     float red; 3     float green; 4     float blue; 5 } Colour; 6  7 int main() { 8     Colour c = { .red = 0.1, .green = 0.5, .blue = 0.9 }; 9     return 0;10 }
复制代码


在 C ++中,你必须像这样初始化:Colour c = {0.1,0.5,0.9}; 这对于 Color 的定义更改来说更难阅读并且不健壮。我听说指定的初始化程序未来将会在 C ++ 20 中实现,不过已经等了 21 年了。


原文链接:


C++ is not a superset of C


2019-09-10 00:084423
用户头像
王文刚 Instagram 营销专家

发布了 37 篇内容, 共 24.5 次阅读, 收获喜欢 55 次。

关注

评论

发布
暂无评论
发现更多内容

高级产品经理必备素养

产品海豚湾

产品经理 职业发展 商业化 9月月更 产品价值

华为云快成长GaussDB(for Redis)

IT资讯搬运工

【CSS·清除浮动】Clearing floats:clear、clearfix、overflow、flow-root

翼同学

CSS 前端 9月月更

数据可视化系列教程之组件构成

云智慧AIOps社区

前端 低代码 开源项目 数据可视化 可视化大屏

新书上市 | 连载 5 年,千万读者追更,这本书讲透了通信背后的故事!

图灵社区

通信技术 科技史

数据中心网络架构的需求原则及策略

阿泽🧸

数据中心 9月月更

跟着卷卷龙一起学Camera--黑电平Blacklevel

卷卷龙

ISP camera 9月月更

【CSS】:正常布局流(normal flow)、有趣的浮动(float)

翼同学

CSS 前端 9月月更

如何增强 ABAP 系统,允许开发人员给 ABAP 对象设置标签,方便快速检索

汪子熙

SAP abap Netweaver 标签体系 9月月更

深耕隐私计算技术,瓴羊DataTrust团队喜获殊荣

瓴羊企业智能服务

Nacos 安装教程(史上最详细保姆级教程)

nacos SpringCloud 9月月更

玖章算术受邀参加红杉Talk「创新的复利」科技专场,共同探讨云计算的前世今生

数据库 数据复制 数据管理 数据备份 玖章算术

Linux面试最高频的5个基本问题

千锋IT教育

关于链路追踪所需要了解的知识

穿过生命散发芬芳

链路追踪 9月月更

实践分享!GitLab CI/CD 快速入门

北京好雨科技有限公司

企业号九月金秋榜 Kuberetes

Predicate

急需上岸的小谢

9月月更

Web3大行其道,为何说Zebec值得投资人期待?

BlockChain先知

2022-09-01:字符串的 波动 定义为子字符串中出现次数 最多 的字符次数与出现次数 最少 的字符次数之差。 给你一个字符串 s ,它只包含小写英文字母。请你返回 s 里所有 子字符串的 最大波

福大大架构师每日一题

算法 rust语言 福大大

NodeJs小试牛刀--聊天室搭建

zxhtom

9月月更

测试需求平台3-登录打通和产品列表功能实现

MegaQi

测试平台开发教程 9月月更

从用户到开发者是一种思维进化过程 | 访 StarRocks Committer 周威

StarRocks

k8s自定义controller三部曲之一:创建CRD(Custom Resource Definition)

程序员欣宸

Kubernetes k8s 9月月更

C++学习------cassert头文件的作用与源码学习

桑榆

c++ 9月月更

【CSS·显示类型】 block、inline以及inline-block

翼同学

CSS 前端 9月月更

高并发场景下,6种方案,保证缓存和数据库的最终一致性!

C++后台开发

数据库 缓存 高并发 后端开发 C++开发

万物皆可集成系列:低代码对接企企云实现数据集成

葡萄城技术团队

基于 xbot 实现微信关键词自动回复

Hanson

微信 微信机器人 自动回复

maven入门

楠羽

maven 笔记 9月月更

# 靠谱:开源IM项目OpenIM压测程序介绍-自己动手压测性能和稳定性

Geek_1ef48b

leetcode 206. Reverse Linked List 反转链表(简单)

okokabcd

LeetCode 数据结构与算法

以数字技术赋能产业金融生态能力建设,破解银行的场景焦虑

易观分析

金融 银行 数字科技

C++不是C的超集!_编程语言_lochsh_InfoQ精选文章