56

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

一位评论者回答

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

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

因为我只用 MS C++ 编程,所以我真的不太了解“纯”C,即我没有现成的图片了解我使用的 C++ 语言的哪些细节不在 C(99) 中,而且我几乎没有一些 C99 代码在 C++ 编译器中无法按原样工作的线索。

请注意,我知道 C99 onlyrestrict关键字对我来说似乎具有非常狭窄的应用和可变长度数组(我不确定它们的广泛性或重要性)。

此外,我对是否存在任何重要的语义差异或陷阱非常感兴趣,即 C(99) 代码将在 C++(11) 下编译,但使用 C++ 编译器的操作与使用 C 编译器不同。


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

4

4 回答 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 中的新特性非常适合一般编程。VLA 并且restrict(在我看来)不是针对一般用途的,而是主要用于将 FORTRAN 和数值程序员带到 C 中(尽管restrict有助于自动矢量化器)。由于如果您在文件的顶部,任何使用的符合标准的程序restrict仍然会以完全相同的方式工作(但可能不会那么快)#define 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 前端。

于 2012-05-05T12:49:24.400 回答
28

如果您从 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 之间的不兼容列表(Mat 已经提到过)。

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

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

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

于 2012-05-05T12:56:24.290 回答
2

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

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

于 2012-05-05T15:05:15.997 回答
2

我将提到C++11 标准的“C.1 C++ 和 ISO C” 。该文件对每一个差异及其对发展的影响进行了逐一阐述。

于 2012-05-06T14:36:03.240 回答