写点什么

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

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

关注

评论

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

吃透负载均衡

高性能架构探索

负载均衡 架构 分布式 微服务 签约计划第二季

Flyway让数据库版本管理更简单

恒生LIGHT云社区

数据库 sql SqlServer

工具链建设的必要性

刘宇

云计算 Serverless 云原生 工具链

java开发之Redis分布式锁相关知识分享

@零度

Java redis

面试官:如何实现 List 集合去重?

王磊

java面试

前端架构师修炼指南精选

杨成功

前端 架构师 内容合集 签约计划第二季

实用机器学习笔记十四:多层感知机

打工人!

人工智能 机器学习 算法 学习笔记 12月日更

有了代码变更分解提交工具SmartCommit,再也不担心复合提交了

华为云开发者联盟

代码 复合提交 SmartCommit 代码提交 代码提交原子性

欢迎举报Perforce Helix Core盗版行为

龙智—DevSecOps解决方案

盗版软件 perforce盗版 打击盗版

技术实力过硬,旺链科技斩获“年度区块链技术突破奖”!

旺链科技

区块链 区块链技术 产业区块链

Homebrew大神面试Google被拒,只因写不出一道算法题

博文视点Broadview

亿级流量实验平台设计与实现

高性能架构探索

架构 分布式 微服务 签约计划第二季 实验平台

彻底搞通服务发现的原理和实现

高性能架构探索

架构 分布式 微服务 服务发现 签约计划第二季

给弟弟的信第13封|一个北京姑娘的艰辛生活

大菠萝

28天写作

Linux中国对话龙蜥社区4位理事:龙蜥操作系统捐赠的背后,是谁在推动?

OpenAnolis小助手

Linux 国产操作系统 龙蜥社区

鸿蒙轻内核源码分析:MMU协处理器

华为云开发者联盟

鸿蒙 MMU协处理器 ARM CP15协处理器 协处理器 MMU

Elasticsearch 查询最大时间(qbit)

qbit

sql UTC TimeZone 时区

openLooKeng社区Apache Log4j2高危安全漏洞修复完成,建议用户升级

LooK

大数据 安全漏洞 openLooKeng Apache Log4j2 远程代码执行

Serverless 架构简介

刘宇

云计算 Serverless 云原生 Serverless Devs

Java常用的五大算法详解

编程江湖

算法 java编程

阿里云田涛涛解读未来自动化运维新思路:CloudOps

阿里云弹性计算

CloudOps 云上运维

lock-free在召回引擎中的实现

高性能架构探索

架构 分布式 微服务 签约计划第二季

用户文章转载:一图看懂 | 我用这张图,看懂了 P4 Reconcile

龙智—DevSecOps解决方案

perforce 一图看懂 P4 Reconcile

智能运维之时间序列预测中的经典时序模型

云智慧AIOps社区

机器学习 算法 智能运维 云智慧 指标预测

一文带你熟知ForkJoin

华为云开发者联盟

jdk 并发编程 并发 forkjoin 多线程并发

C++ 开发笔记

行者孙

内容合集 签约计划第二季

带波浪效果的CollapsingToolbarLayout + RecycleView

阿策小和尚

28天写作 Android 小菜鸟 12月日更

JerryScript:物联网开发者的得力工具

华为云开发者联盟

物联网 LiteOS JerryScript 引擎 物联网应用

【LeetCode】寻找旋转排序数组中的最小值Java题解

Albert

算法 LeetCode 12月日更

流量控制-从原理到实现

高性能架构探索

架构 分布式 微服务 签约计划第二季

从云计算到 Serverless

刘宇

云计算 Serverless 云原生 Serverless Devs

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