写点什么

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

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

关注

评论

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

阿里全新推出:微服务突击手册,把所有操作都写出来了|超清PDF

Java 架构 微服务 Spring Cloud spring cloud alibaba

本周日直播,全链路数据治理实践论坛开放报名

阿里云大数据AI技术

大数据 数据治理

k8s+Docker部署方法

Java你猿哥

Java Docker k8s ssm 架构师

责任链模式在复杂数据处理场景中的实战

阿里技术

设计模式 技术实践

一周吃透Java面试八股文(2023最新整理)

Java你猿哥

Java kafka Spring Boot JVM java面试

MySQL百万数据深度分页优化思路分析

Java你猿哥

Java MySQL 数据库 ssm 优化技术

震撼来袭!最具中国特色的微服务组件:新一代SpringCloud Alibaba

Java 架构 微服务 Spring Cloud spring cloud alibaba

牛掰!阿里架构师熬夜肝了一份JVM必知必会,哪里不会查哪里

Java 性能优化 JVM

开源之夏 2023 | 与 Databend 一同探索云数仓的魅力

Databend

用户分享 | Dockquery,一个国产数据库客户端的初体验

BinTools图尔兹

用户体验 国产数据库工具

重磅发布!阿里巴巴专家亲自撰写,Dubbo 3.0 分布式实战(彩印版)

Java 分布式 微服务 dubbo

云数据库技术沙龙|多云多源下的数据复制技术解读-NineData

NineData

MySQL Clickhouse 数据管理 多云多源 数据存取

Java并发编程必备:分布式锁的选型和性能对比

Java 数据库 分布式锁

零信任是什么意思?与堡垒机有什么区别?

行云管家

网络安全 堡垒机 零信任

新技术越来越多,作为程序员,我们应该怎么规划职业生涯? | 社区征文

wljslmz

三周年征文

基于图神经网络的推荐算法

TiAmo

神经网络 算法 推荐算法

AI 大底座,大模型时代的答卷

百度Geek说

人工智能 百度 文心一言 企业号 5 月 PK 榜

精选!字节大佬带你一周刷完Java面试八股文,比啃书效果好多了

Java你猿哥

Java 算法 ssm java面试 java知识点

鲸鸿动能广告接入如何高效变现流量?

HarmonyOS SDK

HMS Core

sysMaster: 全新1号进程实现方案,秒级自愈,保障系统全天在线

openEuler

Linux rust 操作系统 openEuler init

硬核Prompt赏析:HuggingGPT告诉你Prompt可以有多“工程”

无人之路

ChatGPT Prompt

并发编程-ReentrantLook底层设计

Java你猿哥

Java ssm 重入锁 lock锁 底层实现原理

阿里官方上线!号称Java面试八股文天花板(2023最新版)首次开源

Java你猿哥

Java redis Spring Boot JVM java面试

独家巨献!阿里专家兼Github贡献者,整理的SpringBoot入门到成神

Java spring 架构 微服务 Spring Boot

Redis和MySQL扛不住,B站分布式存储系统如何演进?

Java你猿哥

Java MySQL redis ssm kv

从浏览器输入域名开始分析DNS解析过程

华为云开发者联盟

开发 华为云 DNS 华为云开发者联盟 企业号 5 月 PK 榜

如何选择最优权限框架?Sa-Token 和 Shiro 对比

shiro Sa-Token

硬核!阿里最新出品架构核心场景实战手册,解决99%的架构问题

Java你猿哥

微服务 架构设计 架构师 架构场景实战 微服务实战

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