写点什么

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:084434
用户头像
王文刚 Instagram 营销专家

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

关注

评论

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

易点天下入选虎嗅智库大鲸榜AIGC数字营销技术商TOP15

新消费日报

前端之Web Components 的潜力与挑战

高端章鱼哥

前端 Web Components

拓展海外社媒市场,云手机抢占先机

Ogcloud

云手机 海外云手机 云手机海外版 社媒运营 社媒营销

人民日报媒体技术公司与华为达成鸿蒙合作 助力超千家党媒单位融入鸿蒙生态

最新动态

云电脑如何选择使用?详细介绍

青椒云云电脑

云电脑 云电脑平台

用一个数据库解决80%的问题,YashanDB数据库一体化的实践

Geek_2d6073

软件测试学习笔记丨业务架构分析工具 plantuml

测试人

软件测试

关于数据动态脱敏,业务部与安全部终于不用battle了!

极盾科技

数据安全

京东广告研发——效率为王:广告统一检索平台实践

京东科技开发者

颜水成挂帅,昆仑万维2050全球研究院联合NUS、NTU发布Vitron,奠定通用视觉多模态大模型终极形态

新消费日报

HCDG天津站精彩回顾 | AI高效开发, ModelArts技术动手工作坊

华为云开发者联盟

人工智能 华为云 华为云开发者联盟 企业号2024年4月PK榜 华为云HCDG

万物生长,数智融合,又拍云荣登「2024杭州准独角兽企业榜单」

Geek_2d6073

青椒云桌面玩转AIGC应用部署

青椒云云电脑

云桌面 AIGC

TikTok海外直播网络专线的优势有哪些?

Ogcloud

海外直播专线 海外直播 tiktok直播 tiktok直播专线 海外直播网络

C++ 构造函数实战指南:默认构造、带参数构造、拷贝构造与移动构造

快乐非自愿限量之名

Java c++ 函数 开发语言

三十分钟入门基础Go(Java小子版)

京东科技开发者

安全测试之探索windows游戏扫雷

京东科技开发者

AIGC硬件成本太高?试试云电脑

青椒云云电脑

云电脑 AIGC

领跑数字化转型:望繁信科技荣登「2024智能自动化技术商Top 15」榜单

望繁信科技

数据挖掘 流程挖掘 流程资产 流程智能

深度解析阿里巴巴1688商品详情API返回值:商品信息一手掌握

技术冰糖葫芦

DDD领域驱动设计总结和C#代码示例

EquatorCoco

C# 软件开发 DDD

服务于金融新核心系统 XSKY星辰天合与中电金信完成产品兼容认证

XSKY星辰天合

XSKY 星辰天合 金融新核心

如何基于Django中的WebSockets和异步视图来实现实时通信功能

不在线第一只蜗牛

django Web websockets

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