【问题标题】:What are the incompatible differences between C(99) and C++(11)?C(99) 和 C++(11) 有什么不兼容的区别?
【发布时间】:2012-05-14 17:39:56
【问题描述】:

这个问题是由对post by Herb Sutter 的回复触发的,他在其中解释了 MS 决定不支持/制作 C99 编译器,而只是使用 C++(11) 标准中的 C(99) 功能.

一个commenter replied

(...) C 很重要,至少值得关注。

有很多现有的代码是有效的 C 但不是 有效的 C++。该代码不太可能被重写(...)

由于我只使用 MS C++ 编程,我真的不太了解“纯”C,即我不知道我使用的 C++ 语言的哪些细节不在 C(99) 中,并且我几乎没有线索表明某些 C99 代码在 C++ 编译器中无法按原样工作。

请注意,我只知道 C99 关键字 restrict 对我来说似乎的应用范围非常狭窄,并且关于可变长度数组(我不确定其中的广泛性或重要性)他们是)。

另外,我很感兴趣是否存在任何重要的语义差异或陷阱,即 C(99) 代码将在 C++(11) 下编译,但在 C++ 下会做不同的事情编译器比使用 C 编译器。


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

【问题讨论】:

  • C++11 还支持变长数组吗? C99 有,所以如果没有,这是一个很好的例子。
  • david.tribble.com/text/cdiffs.htm - 其中很多对于 C++11 仍然有效。 @CodyGray:VLA 不是 C++11 的一部分。
  • @CodyGray:C++11 不支持 VLA,所以你的评论可以作为答案:)
  • “请注意,我知道...关于可变长度数组”。
  • 我看到了,如果他无法想象可变长度数组的用途或重要性,我觉得他不可能知道什么是可变长度数组。 :-)

标签: c++ c c++11 c99


【解决方案1】:

有很多不兼容问题已经存在了很长时间(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 中的新功能非常适合一般编程。 VLA 和restrict(在我看来)不是针对一般用途的,而是主要用于将 FORTRAN 和数值程序员带到 C 中(尽管restrict 有助于自动矢量化器)。因为如果您在文件顶部使用#define restrict,任何使用restrict 的符合标准的程序仍将以完全相同的方式工作(但可能不会那么快),这没什么大不了的。 VLA 似乎在野外相当罕见。

灵活的数组成员可以很好。请注意,这些与可变长度数组不同!多年来人们一直在使用这个技巧,但官方支持意味着更少的输入,它还允许我们在编译时创建常量。 (旧的方法是有一个大小为 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 关键字的行为不同,您可以通过向该单元添加 extern 声明来选择哪个翻译单元获取声明为内联的函数的非内联版本。

复合文字。

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 语言中最有用的 10 个库函数。它不仅在 C++ 中缺失,而且 MSVC 运行时只提供了一个名为 _snprintf 的函数, 不保证会在字符串中添加 NUL 终止符。snprintf 在 C++11 中,但在 MSVC C 运行时中仍然明显不存在。)

匿名结构和 联合(C11,但 GCC 永远扩展)(匿名联合显然在 C++03 中,在 C 模式下不支持 MSVC):

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

如您所见,其中许多功能只是为您节省大量输入(复合文字),或使程序更易于调试(灵活的数组成员),更容易避免错误(指定初始化程序/忘记初始化结构字段)。这些变化并不大。

对于语义差异,我确信别名规则是不同的,但是现在大多数编译器都足够宽容,我不确定您将如何构建一个测试用例来演示。每个人都知道的 C 和 C++ 之间的区别是旧的sizeof('a') 表达式,对于 C++,它始终为 1,但在 32 位 C 系统上通常为 4。但没人关心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 前端。

【讨论】:

  • C99 不允许灵活的数组成员初始化(它是 GNU 扩展)。 C++11 有统一初始化,类似于复合字面量:return {cos(angle)*r,sin(angle)*r};。匿名联合是有效的 C++。我不确定匿名结构是否是有效的 C 语言(它们对我来说似乎毫无意义)。我认为“语义上可疑的 C++”是有效的,但我可能错了。
  • C++ 也很好,任何地方的 C 程序员都应该使用它。 ;)
  • “inline 关键字的行为不同,您可以通过向该单元添加 extern 声明来选择哪个翻译单元获取声明为 inline 的函数的非内联版本。”。不幸的是你have to选择:(
  • 关于最后一个例子:它是有效的 C++11。当在union 中使用具有公共前导序列(此处为tag)的标准布局类型时,如果设置了其中一个,则可以通过它们中的任何一个访问形成公共前导序列的属性。您可以在 [class.mem](内存中的注释 17)中了解它,当然,它只是源于编译器在布局类型时没有执行任何魔法的事实:)
  • @bames53:我不会在 C++ 论坛上四处走动,告诉他们使用 C#,也不要在 C 的讨论中告诉人们使用 C++。
【解决方案2】:

如果您从 C 和 C++ 的公共子集开始,有时称为干净 C(不完全是 C90),则必须考虑 3 种类型的不兼容性:

  1. 使合法 C 变为非法 C++ 的其他 C++ 功能

    这方面的示例是 C++ 关键字,可用作 C 中的标识符或在 C 中隐含但需要在 C++ 中显式转换的转换。

    这可能是微软仍然发布 C 前端的主要原因:否则,必须重写不能编译为 C++ 的遗留代码。

  2. 不属于 C++ 的其他 C 功能

    在 C++ 分叉后,C 语言并没有停止发展。一些例子是可变长度数组、指定的初始化器和restrict。这些功能可能非常方便,但不是任何 C++ 标准的一部分,其中一些可能永远不会进入。

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

    const 对象或inline 函数的链接就是一个例子。

C99 和 C++98 can be found here 之间的不兼容列表(Mat 已经提到)。

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

顺便说一句,虽然微软对放弃 C 的决定(这不是最近的决定)采取了一些态度,但据我所知,开源社区中没有人真正采取措施对此采取措施:很可能通过 C-to-C++ 编译器提供现代 C 的许多特性,特别是如果您考虑到 some of them are trivial to implement。这实际上现在可以使用支持 C99 的 Comeau C/C++。

不过,这并不是一个真正紧迫的问题:就我个人而言,我对在 Windows 上使用 GCC 和 Clang 感到非常满意,而且还有一些专有的 MSVC 替代方案,例如 Pelles C 或英特尔的编译器。

【讨论】:

    【解决方案3】:

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

    维基百科page上列出了许多其他差异。

    【讨论】:

    • 我认为这也是 C99 中的 UB,只有 OpenCL C(基于 C99)稍微失去了这个限制。
    • [wikipedia section}(en.wikipedia.org/wiki/…) 确实不错。在问这里之前,我应该养成访问维基百科的习惯。 :-)
    • 我认为 C++ 提供了“reinterpret_cast”,但是,这是在原始类型中的位模式在目标类型中有意义的情况下定义的行为。 C 没有提供类似的东西。
    • @supercat 不,大多数情况下,不兼容类型之间的reinterpret_cast 违反了严格的别名规则,几乎与C 中相同。即使unsigned int 的所有位零都具有积极意义零float,对指向前者的指针执行reinterpret_cast 指向后者的指针,使用结果为UB。
    【解决方案4】:

    我会提到C++11 standard 的“C.1 C++ 和 ISO C”。该文件对每个差异及其对发展的影响逐一进行了说明。

    【讨论】:

      猜你喜欢
      • 2021-08-31
      • 1970-01-01
      • 1970-01-01
      • 2011-10-16
      • 2012-05-31
      • 2011-04-05
      相关资源
      最近更新 更多