在 C 和 C 中都有效的代码可以在每种语言编译时产生不同的行为C++

Can code that is valid in both C and C++ produce different behavior when compiled in each language?

本文关键字:C++ 编译 有效 代码 语言      更新时间:2023-10-16

C和 C++ 有很多区别,并非所有有效的 C 代码都C++代码有效。
(我所说的"有效"是指具有定义行为的标准代码,即不是特定于实现/未定义的/等。

是否存在任何情况,其中一段在 C 和 C++ 中都有效的代码在使用每种语言的标准编译器进行编译时会产生不同的行为?

为了使它成为一个合理/有用的比较(我试图学习一些实际有用的东西,而不是试图在问题中找到明显的漏洞),让我们假设:

没有
  • 与预处理器相关的内容(这意味着没有使用#ifdef __cplusplus、编译指示等进行黑客攻击)
  • 任何
  • 实现定义的内容在两种语言中都是相同的(例如数字限制等)
  • 我们正在比较每个标准的合理最新版本(例如,C++98 和 C90 或更高版本)
    如果版本很重要,那么请说明每个标准的哪些版本会产生不同的行为。

下面是一个示例,它利用了 C 和 C++ 中的函数调用和对象声明之间的差异,以及 C90 允许调用未声明函数的事实:

#include <stdio.h>
struct f { int x; };
int main() {
f();
}
int f() {
return printf("hello");
}

在C++中,这将不打印任何内容,因为会创建和销毁临时f,但在 C90 中,它将打印hello因为可以在没有声明的情况下调用函数。

如果你想知道这个名字f被使用了两次,C 和 C++ 标准明确允许这样做,并且要创建一个对象,你必须说struct f消除歧义,如果你想要结构,或者如果你想要函数,就省略struct

对于C++与 C90,至少有一种方法可以获得未定义实现的不同行为。C90 没有单行注释。稍微小心一点,我们可以使用它来创建在 C90 和 C++ 中具有完全不同的结果的表达式。

int a = 10 //* comment */ 2 
+ 3;

在C++中,从//到行尾的所有内容都是注释,因此计算为:

int a = 10 + 3;

由于 C90 没有单行注释,因此只有/* comment */是注释。第一个/2都是初始化的一部分,因此得出:

int a = 10 / 2 + 3;

因此,正确的C++编译器将给出 13,但严格正确的 C90 编译器将给出 8。当然,我只是在这里选择了任意数字 - 你可以根据需要使用其他数字。

以下在 C 和 C++ 中有效,将(很可能)在 C 和 C++ 中i产生不同的值:

int i = sizeof('a');

有关差异的说明,请参阅 C/C++ 中的字符大小 ('a')。

本文中的另一个:

#include <stdio.h>
int  sz = 80;
int main(void)
{
struct sz { char c; };
int val = sizeof(sz);      // sizeof(int) in C,
// sizeof(struct sz) in C++
printf("%dn", val);
return 0;
}

C90 vs. C++11 (intvs.double):

#include <stdio.h>
int main()
{
auto j = 1.5;
printf("%d", (int)sizeof(j));
return 0;
}

在 C 中,auto表示局部变量。在 C90 中,可以省略变量或函数类型。它默认为int.在 C++11 中,auto的含义完全不同,它告诉编译器从用于初始化变量的值推断变量的类型。

另一个我还没有看到的例子,这个突出了预处理器的差异:

#include <stdio.h>
int main()
{
#if true
printf("true!n");
#else
printf("false!n");
#endif
return 0;
}

这将在 C 中打印"false",在C++中打印"true" - 在 C 中,任何未定义的宏的计算结果为 0。 在C++中,有 1 个例外:"true"的计算结果为 1。

根据 C++11 标准:

a.逗号运算符在 C 语言中执行左值到右值的转换,但不执行C++:

char arr[100];
int s = sizeof(0, arr);       // The comma operator is used.

在C++中,此表达式的值将为 100,在 C 中,此值将为sizeof(char*)

b.在C++中,枚举器的类型是其枚举。在 C 中,枚举器的类型为 int。

enum E { a, b, c };
sizeof(a) == sizeof(int);     // In C
sizeof(a) == sizeof(E);       // In C++

这意味着sizeof(int)可能不等于sizeof(E)

c.C++用空参数列表声明的函数不带参数。在C中,空参数列表意味着函数参数的数量和类型是未知的。

int f();           // int f(void) in C++
// int f(*unknown*) in C

该程序以 C++ 打印1,用 C 打印0

#include <stdio.h>
#include <stdlib.h>
int main(void)
{
int d = (int)(abs(0.6) + 0.5);
printf("%d", d);
return 0;
}

发生这种情况是因为C++中存在double abs(double)重载,因此abs(0.6)返回0.6而在 C 中,由于在调用int abs(int)之前隐式的双精度转换为整数,它返回0。在 C 中,您必须使用fabs才能使用double

另一个sizeof陷阱:布尔表达式。

#include <stdio.h>
int main() {
printf("%dn", (int)sizeof !0);
}

它等于 C 中的sizeof(int),因为表达式的类型为int,但通常为 1/C++(尽管不是必需的)。在实践中,它们几乎总是不同的。

#include <stdio.h>
int main(void)
{
printf("%dn", (int)sizeof('a'));
return 0;
}

在 C 语言中,这会打印当前系统上sizeof(int)的任何值,这在当今常用的大多数系统中通常4

在C++中,这必须打印 1。

一个依赖于 C 编译器的老栗子,无法识别C++行尾注释......

...
int a = 4 //* */ 2
+2;
printf("%in",a);
...

C++ Programming Language(第3版)给出了三个例子:

  1. sizeof('a'),正如@Adam罗森菲尔德所提到的;

  2. //用于创建隐藏代码的注释:

    int f(int a, int b)
    {
    return a //* blah */ b
    ;
    }
    
  3. 结构等将内容隐藏在范围之外,如您的示例所示。

C++标准列出的另一个:

#include <stdio.h>
int x[1];
int main(void) {
struct x { int a[2]; };
/* size of the array in C */
/* size of the struct in C++ */
printf("%dn", (int)sizeof(x)); 
}

C 中的内联函数默认为外部作用域,而 C++ 中的内联函数则不然。

在GNU C 的情况下,将以下两个文件编译在一起将打印"我是内联的",但在 C++ 中没有任何内容。

文件 1

#include <stdio.h>
struct fun{};
int main()
{
fun();  // In C, this calls the inline function from file 2 where as in C++
// this would create a variable of struct fun
return 0;
}

文件 2

#include <stdio.h>
inline void fun(void)
{
printf("I am inlinen");
} 

此外,C++隐式地将任何全局const视为static,除非它被明确声明为extern,不像C,其中extern是默认值。

#include <stdio.h>
struct A {
double a[32];
};
int main() {
struct B {
struct A {
short a, b;
} a;
};
printf("%dn", sizeof(struct A));
return 0;
}

该程序在使用C++编译器编译时打印128(32 * sizeof(double)),在使用C编译器编译时打印4

这是因为 C 没有作用域解析的概念。在C中,包含在其他结构中的结构被放入外部结构的范围内。

struct abort
{
int x;
};
int main()
{
abort();
return 0;
}

返回时退出代码在 C++ 中为 0,在 C 中为 3。

这个技巧可能可以用来做一些更有趣的事情,但我想不出一个好的方法来创建一个适合 C 的构造函数。我尝试用复制构造函数制作一个同样无聊的示例,这将允许传递参数,尽管以一种相当不可移植的方式:

struct exit
{
int x;
};
int main()
{
struct exit code;
code.x=1;
exit(code);
return 0;
}

然而,VC++ 2005拒绝在C++模式下编译它,抱怨"退出代码"是如何重新定义的。(我认为这是一个编译器错误,除非我突然忘记了如何编程。不过,当编译为 C 时,它以进程退出代码 1 退出。

不要忘记 C 和 C++ 全局命名空间之间的区别。假设你有一个foo.cpp

#include <cstdio>
void foo(int r)
{
printf("I am C++n");
}

和一个foo2.c

#include <stdio.h>
void foo(int r)
{
printf("I am Cn");
}

现在假设你有一个main.cmain.cpp它们看起来都像这样:

extern void foo(int);
int main(void)
{
foo(1);
return 0;
}

当编译为 C++ 时,它将使用 C++ 全局命名空间中的符号;在 C 中,它将使用 C 命名空间:

$ diff main.cpp main.c
$ gcc -o test main.cpp foo.cpp foo2.c
$ ./test 
I am C++
$ gcc -o test main.c foo.cpp foo2.c
$ ./test 
I am C
int main(void) {
const int dim = 5; 
int array[dim];
}

这是相当奇特的,因为它在C++和C99,C11和C17中有效(尽管在C11,C17中是可选的);但在C89中无效。

在 C99+ 中,它创建了一个可变长度数组,该数组与普通数组相比具有自己的特点,因为它具有运行时类型而不是编译时类型,并且sizeof array不是 C 中的整数常量表达式。在C++中,类型是完全静态的。


如果尝试在此处添加初始值设定项:

int main(void) {
const int dim = 5; 
int array[dim] = {0};
}

C++有效,但 C 无效,因为可变长度数组不能具有初始值设定项。

空结构的大小在 C 中为 0,在 C++ 中为 1:

#include <stdio.h>
typedef struct {} Foo;
int main()
{
printf("%zdn", sizeof(Foo));
return 0;
}

这涉及 C 和 C++ 中的左值和右值。

在 C 编程语言中,前递增运算符和后递增运算符都返回右值,而不是左值。这意味着它们不能位于=赋值运算符的左侧。这两个语句都会在 C 中给出编译器错误:

int a = 5;
a++ = 2;  /* error: lvalue required as left operand of assignment */
++a = 2;  /* error: lvalue required as left operand of assignment */

但是,C++,前递增运算符返回左值,而后递增运算符返回右值。这意味着带有预递增运算符的表达式可以放置在=赋值运算符的左侧!

int a = 5;
a++ = 2;  // error: lvalue required as left operand of assignment
++a = 2;  // No error: a gets assigned to 2!

为什么会这样呢?后增量递增变量,并返回增量发生的变量。这实际上只是一个右值。变量 a 的前一个值作为临时值复制到寄存器中,然后 a 递增。但是 a 的前一个值是由表达式返回的,它是一个右值。它不再表示变量的当前内容。

预增量首先递增变量,然后返回增量发生变为的变量。在这种情况下,我们不需要将变量的旧值存储到临时寄存器中。我们只是在变量递增后检索它的新值。所以预递增返回一个左值,它返回变量 a 本身。我们可以使用将此左值分配给其他东西,它类似于以下语句。这是左值到右值的隐式转换。

int x = a;
int x = ++a;

由于预递增返回一个左值,我们也可以为其分配一些东西。以下两个语句是相同的。在第二个赋值中,首先 a 递增,然后用 2 覆盖其新值。

int a;
a = 2;
++a = 2;  // Valid in C++.