C(99)和C++(11)之间存在哪些不兼容的差异?

57

这个问题是由Herb Sutter的一篇文章引发的,他在其中解释了微软决定不支持/制作C99编译器,而只采用C++(11)标准中已有的C(99)特性。

其中一位评论者回复:

(...) C语言很重要,值得至少一点关注。

现有许多有效的C代码不是有效的C ++。这些代码不太可能被重新编写(...)

由于我只在MS C++中编程,因此我不太了解"纯" C,即我没有对我正在使用的C++语言的哪些细节不在C(99)中有一个明确的印象,也不知道某些C99代码在C++编译器中不能原样工作。

请注意,我只了解C99中的restrict关键字,它似乎只适用于非常狭窄的应用程序,而变长数组我不确定它们有多普及或重要。

此外,我非常想知道是否存在任何重要的语义差异或陷阱,也就是说,在C++(11)下编译的C(99)代码会与C编译器产生不同的结果。


快速链接:答案中的外部资源:


9
C++11是否支持可变长数组?C99支持,如果不支持,那就是一个很好的例子。 - Cody Gray
2
http://david.tribble.com/text/cdiffs.htm - 其中很多内容仍然适用于C++11。@CodyGray:可变长度数组不是C++11的一部分。 - Mat
1
@CodyGray:C++11 不支持 VLA,所以你的评论可以作为一个答案 :) - Alok Save
2
请注意,我对可变长度数组一无所知。 - Niklas B.
2
我看到了那个,感觉他如果无法想象可变长度数组的用途或重要性,那么他可能不知道它是什么。 :-) - Cody Gray
好吧,我还没有读过Herb Sutter的帖子,但是即使在新的VC11中,他们甚至没有包括C++11中的C99部分,我听说它声称有完整的C++标准库,但它仍然缺少像<cfenv>或来自<cmath>的新浮点函数这样的东西。 - Christian Rau
4个回答

32

有一堆不兼容性已经存在了很长时间(C90或更早),以及一堆非常好的特性在C99和C11中。这些都是我能想到的。

// Valid C
int *array = malloc(sizeof(*array) * n);

// Valid C and valid C++, extra typing, it's always extra typing...
int *array = (int *) malloc(sizeof(*array) * n);

// Valid C++
int *array = new int[n];

C99很好,C程序员应该普遍使用它

C99的新特性对于一般编程非常有用。虽然可变长度数组和restrict并不是(在我看来)针对一般用途设计的,但主要是为了将FORTRAN和数值程序员引入到C语言中(尽管restrict有助于自动向量化器)。由于任何使用restrict的符合规范的程序仍然会以完全相同的方式工作(但可能不太快),如果在文件顶部#define restrict,那么这并不是一个大问题。在实际开发中,可变长度数组似乎比较少见。

灵活数组成员可以很好用。请注意,这些不同于可变长度数组!人们已经使用这个技巧多年了,但官方支持意味着更少的输入,并且它还允许我们在编译时制定常量。(旧方法是创建大小为1的数组,但计算分配大小确实很麻烦。)

struct lenstr {
    unsigned length;
    char data[];
};
// compile time constant
const struct lenstr hello = { 12, "hello, world" };

指定初始化器。可以节省很多打字时间。

struct my_struct { int a; char *b; int c; const char *d; };
struct my_struct x = {
    .a = 15,
    .d = "hello"
    // implicitly sets b = NULL and c = 0
};
int hex_digits[256] = { ['0'] = 0, ['1'] = 1, ['2'] = 2, /* etc */ ['f'] = 15 };

inline关键字的行为是不同的,您可以选择在该单元中添加外部声明以获取未声明为inline的函数的非inline版本。

复合字面量。

struct point { float x; float y; };
struct point xy_from_polar(float r, float angle)
{
    return (struct point) { cosf(angle) * r, sinf(angle) * r };
}

snprintf 函数 可能是我在C语言中使用最多的前十个库函数之一。它不仅在C++中缺失,而且MSVC运行时只提供了一个名为_snprintf的函数,该函数不能保证将NUL终止符添加到字符串中。snprintf 在 C++11 中出现,但仍然明显缺少 MSVC C 运行时支持)

匿名结构体和共用体 (自从很久以前就有GCC扩展,C11标准中包括它们)(匿名共用体显然在C++03中存在,但在C模式下没有MSVC支持):

struct my_value {
    int type;
    union {
        int as_int;
        double as_double;
    }; // no field name!
};

正如您所看到的,这些特性只是为了节省许多打字(复合字面量),或使程序更易于调试(灵活的数组成员),更易于避免错误(指定初始化器/忘记初始化结构字段)。这些都不是激烈的变化。

对于语义上的区别,我确定别名规则不同,但大多数编译器现在足够宽容,我不确定您将如何构建测试用例来证明。C和C ++之间的差异是每个人都会谈到的旧sizeof('a')表达式,在32位C系统上通常为4,但在C ++中始终为1。但是,没有人关心sizeof('a')是多少。然而,在C99标准中有一些保证,对现有实践进行了规范化。

考虑以下代码。它使用了一种在C中定义联合类型的常见技巧,可以避免浪费额外的存储空间。我认为这在语义上是有效的C99代码,但我可能错了,C ++则值得怀疑。

#define TAG_FUNKY_TOWN 5
struct object { int tag; };
struct funky_town { int tag; char *string; int i; };
void my_function(void)
{
    struct object *p = other_function();
    if (p->tag == TAG_FUNKY_TOWN) {
        struct funky_town *ft = (struct funky_town *) p;
        puts(ft->string);
    }
}

可惜啊。 MSVC代码生成器很好用,只是没有C99前端。


1
C99 不允许对柔性数组成员进行初始化(这是 GNU 拓展)。C++11 有统一的初始化方式,类似于复合字面量:return {cos(angle)*r,sin(angle)*r};。匿名联合在 C++ 中是合法的。不过我不确定匿名结构在 C 语言中是否合法(而且它们对我来说似乎毫无意义)。我认为“语义上可疑的 C++”是合法的,但我可能错了。 - bames53
5
另外,C++也很好,到处都应该有C程序员使用它。 ;) - bames53
"inline"关键字的行为不同,您可以通过在该单元中添加extern声明来选择哪个翻译单元获得声明为inline的函数的非内联版本。很遗憾,您必须做出选择。 - Johannes Schaub - litb
1
关于最后一个例子:它是有效的C++11代码。当标准布局类型具有共同前导序列(这里是tag)并在union中使用时,如果其中一个被设置,则形成共同前导序列的属性可以通过任何一个访问。您可以在[class.mem](记忆中的注释17)中了解更多信息,当然,这只是源于编译器在布局类型方面不执行任何魔术的事实 :) - Matthieu M.
5
我不会在C++论坛上走来走去告诉他们使用C#,也不会在关于C的讨论中告诉人们使用C++。 - Dietrich Epp
显示剩余5条评论

28
如果您从C和C++的共同子集开始,有时称为干净的C(不完全是C90),则必须考虑三种不兼容性:
  1. 额外的C++特性使合法的C不合法

    这些示例包括C++关键字,在C中可以用作标识符或在C++中需要显式转换的隐式转换。

    这可能是微软仍然提供C前端的主要原因:否则,无法编译为C++的旧代码将必须被重写。

  2. 不是C++的一部分的其他C特性

    C语言在C++分叉后并没有停止发展。一些例子是可变长度数组,指定初始化程序和限定符。这些功能非常方便,但不属于任何C++标准,其中一些可能永远不会出现。

  3. 在C和C ++中都可用,但具有不同语义的功能

    这会导致const对象或inline函数的链接无效。

在这里可以找到C99和C++98之间不兼容的列表(Mat已经提到过了)

虽然C++11和C11在某些方面更加接近(例如可变参数宏现在在C++中也可用,可变长度数组现在是C语言的可选功能),但不兼容性的列表也增长了(例如C中的通用选择和C++中的auto类型说明符)。

顺便说一句,尽管微软因放弃C而受到一些批评(这并不是最近的决定),但据我所知,开源社区中没有人真正采取措施来解决这个问题:通过C-to-C ++编译器很容易提供现代C的许多功能,特别是如果您考虑到其中一些功能非常容易实现。使用Comeau C/C ++现在就可以实现这一点,它支持C99。

然而,这并不是一个紧迫的问题:就个人而言,我在Windows上使用GCC和Clang非常舒适,也有MSVC的专有替代品,例如Pelles C或英特尔编译器。

2

在C++中,设置联合体的一个成员并访问另一个成员的值是未定义的行为,而在C99中不是未定义的。

维基百科页面列出了许多其他差异


我认为在C99中这也是UB,只有基于C99的OpenCL C稍微放宽了这个限制。 - Christian Rau
这个wikipedia部分确实很好。我应该养成在这里询问之前先访问维基百科的习惯。 :-) - Martin Ba
我认为C++提供了“reinterpret_cast”,然而,它在原始类型的位模式在目标类型中具有意义的情况下是定义行为。C语言没有类似的功能。 - supercat
1
@supercat,大多数情况下,在不兼容类型之间使用reinterpret_cast会违反严格别名规则,这与C语言几乎相同。即使所有位的零在unsigned int中作为正零float有意义,将前者的指针强制转换为后者的指针并使用结果也是未定义行为。 - Ruslan

2
我将提到C++11标准中的“C.1 C++和ISO C”。该文档详细列出了每个差异及其对开发的影响。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接