在C和C++中有效的代码在每种语言编译时会产生不同的行为吗?

C和C++有许多不同之处,并非所有有效的C代码C++代码都是有效的。 (通过“有效”我指的是具有定义行为的标准代码,即不是特定于实现/未定义/等)

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

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

  • 没有预处理器相关的(这意味着没有#ifdef __cplusplus,pragmas等黑客)
  • 任何实现定义在两种语言中都是相同的(例如数字限制等)
  • 我们正在比较每个标准的最新版本(例如,C++98和C90或更高版本)
    如果版本很重要,那么请说明每个版本的哪个版本产生不同的行为。
50637 次浏览
#include <stdio.h>


int main(void)
{
printf("%d\n", (int)sizeof('a'));
return 0;
}

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

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

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

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

C++编程语言(第3版)给出了三个例子:

  1. sizeof('a'),正如@Adam Rosenfield提到的;

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

    int f(int a, int b)
    {
    return a //* blah */ b
    ;
    }
    
  3. Structures etc. hiding stuff in out scopes, as in your example.

struct abort
{
int x;
};


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

返回退出代码为0的C++,或3的C。

这个技巧也许可以用来做一些更有趣的事情,但是我想不出一个好的方法来创建一个适合C的构造函数。我试着用复制构造函数做一个类似无聊的例子,这会让一个参数被传递,尽管是以一种相当不可移植的方式:

struct exit
{
int x;
};


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


exit(code);


return 0;
}

然而,VC++2005拒绝在C++模式下编译它,抱怨如何重新定义“退出代码”(我认为这是一个编译器bug,除非我突然忘记了如何编程)。

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("%d\n", (int)sizeof(x));
}

以下在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("%d\n", val);
return 0;
}

下面是一个利用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++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

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

#include <stdio.h>


int main()
{
auto j = 1.5;
printf("%d", (int)sizeof(j));
return 0;
}

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

对于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++中的内联函数则不然。

在GNU C的情况下,将以下两个文件一起编译将打印“I am inline”,但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 inline\n");
}

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

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

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

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

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

#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++打印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中它返回0,因为在调用int abs(int)之前进行了隐式双整型转换。在C中,您必须使用fabs来使用double

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

#include <cstdio>


void foo(int r)
{
printf("I am C++\n");
}

fo2. c

#include <stdio.h>


void foo(int r)
{
printf("I am C\n");
}

现在假设你有一个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
#include <stdio.h>


struct A {
double a[32];
};


int main() {
struct B {
struct A {
short a, b;
} a;
};
printf("%d\n", sizeof(struct A));
return 0;
}

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

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

这涉及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的前一个值由表达式返回,它是一个右值。它不再代表变量的当前内容。

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

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

由于预增量返回一个左值,我们也可以给它赋值。以下两个语句是相同的。在第二个赋值中,第一个a被递增,然后它的新值被2覆盖。

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

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

#include <stdio.h>


typedef struct {} Foo;


int main()
{
printf("%zd\n", sizeof(Foo));
return 0;
}
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,因为可变长度数组不能有初始化器。