【ArchSummit】如何通过AIOps推动可量化的业务价值增长和效率提升?>>> 了解详情
写点什么

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

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

关注

评论

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

喜讯|旺链科技成为TBI数字藏品项目组首批成员

旺链科技

区块链 产业区块链 数字藏品

linux之xargs使用技巧

入门小站

Linux

NVIDIA安培架构下MIG技术分析

天翼云开发者社区

深度解析:DAO的7种常见投票机制

TinTinLand

明源云天际PaaS平台,构建零代码、低代码在线协同开发实践

科技热闻

在线HTML转PHP工具

入门小站

工具

用开源github,还是咱中国自己的代码托管平台云效?

阿里云云效

GitHub 云计算 阿里云 代码管理 代码托管

阿里云云原生一体化数仓入选 2022数博会“十佳大数据案例”

阿里云大数据AI技术

数据挖掘 大数据 分布式计算 数据处理 MaxCompute

Apipost 多人多角色实时协作 爆赞!!!

Xd

数据库 后端 接口测试 API

Neo生态| Polaris Launchpad 黑客松评审现已启动

TinTinLand

LinkedHashMap 源码分析-访问最少删除策略

zarmnosaj

5月月更

直播预告 | PolarDB-X 动手实践系列——基于 Prometheus + Grafana 的 PolarDB-X 监控体系

阿里云数据库开源

数据库 阿里云 开源 PolarDB-X 教学

Apipost 6.0.4版本 支持离线使用

Xd

后端 接口测试 API

PolarDB-X迎来开源后首个重大版本升级,2.1版本新增5大特色功能

阿里云数据库开源

数据库 阿里云 开源 国产数据库 PolarDB-X

一个轻量的数据库数据告警器

山河已无恙

Java 数据监控

如何挑选文档管理软件?

小炮

文档管理

第二章 启航

Geek_古藤模根

图数据库实战 gremlin 入门 Gremlin

在线二进制转文本字符工具

入门小站

工具

分享ApiPost的个人体验感受

Xd

后端 API

两分钟带你体验ApiPost的魅力!

Xd

自动化 接口测试 API

乌卡时代来临,企业供应链管理体系的应对策略

数商云

数字化转型 供应链

10分钟弄懂云原生网络功能,快来瞧瞧!

VoltDB

云原生 云原生网络 网络功能

netty系列之:在netty中使用UDP协议请求DNS服务器

程序那些事

Java Netty 程序那些事 5月月更

网络空间测绘国内外发展及现状

郑州埃文科技

网络安全 IP地址 网络空间测绘技术

月薪 30K 以上的程序员都在学啥?附书单合集

C++后台开发

后端开发 Linux服务器开发 C++后台开发 Linux后台开发 后端开发书籍

uniapp 和 HTML5 区别

CRMEB

EAM与ERP有什么区别?

低代码小观

资产管理 企业管理系统 ERP CRM系统 ERP系统

未来3年,远程办公或成普遍现象,如何提高远程办公效率?

WorkPlus

你会用Apifox写断言吗?

Liam

测试 Postman 自动化测试 测试工具 测试自动化

一个关于SDWAN单臂部署方案验证的实验

天翼云开发者社区

网络

照亮旷野的,是少年开发者眼中的炬火

脑极体

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