698

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

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

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

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

19 回答 19

475

这是一个利用 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

于 2012-10-15T00:19:04.077 回答
447

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

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

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

int a = 10 + 3;

由于 C90 没有单行注释,因此只有/* comment */是注释。first/和 the2都是初始化的一部分,所以结果是:

int a = 10 / 2 + 3;

因此,正确的 C++ 编译器将给出 13,但严格正确的 C90 编译器将给出 8。当然,我在这里选择了任意数字——您可以使用您认为合适的其他数字。

于 2012-10-15T06:03:47.907 回答
408

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

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;
}
于 2012-10-14T23:57:11.920 回答
185

C90 对比 C++11(int对比double):

#include <stdio.h>

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

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

于 2012-10-15T01:42:35.863 回答
125

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

#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。

于 2012-10-17T02:17:59.210 回答
109

根据 C++11 标准:

一种。逗号运算符在 C 但不是 C++ 中执行左值到右值转换:

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

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

湾。在 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
于 2012-10-15T00:48:45.153 回答
55

1该程序在 C++ 和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)重载,因此在 C 中返回abs(0.6)是因为在调用. 在 C 中,您必须使用.0.60int abs(int)fabsdouble

于 2012-12-15T05:24:07.577 回答
40
#include <stdio.h>

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

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

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

于 2009-02-23T23:21:55.293 回答
37

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

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

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

于 2012-10-15T11:46:00.940 回答
26

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

...
int a = 4 //* */ 2
        +2;
printf("%i\n",a);
...
于 2009-02-24T00:05:11.023 回答
26

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

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

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

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

于 2009-02-24T00:07:37.570 回答
22

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)); 
}
于 2009-02-24T00:28:16.913 回答
20

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 inline\n");
} 

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

于 2012-10-15T11:11:09.523 回答
16
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 退出。

于 2009-02-24T00:11:21.260 回答
16
#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++ 编译器编译和使用 C 编译器编译时打印128( ) 。32 * sizeof(double)4

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

于 2015-08-24T16:30:55.503 回答
7

不要忘记 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 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
于 2014-10-17T09:27:43.680 回答
4
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,因为可变长度数组不能有初始值设定项。

于 2018-11-27T06:09:48.197 回答
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++.
于 2018-06-22T03:06:58.233 回答
0

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

#include <stdio.h>

typedef struct {} Foo;

int main()
{
    printf("%zd\n", sizeof(Foo));
    return 0;
}
于 2018-11-07T15:58:39.420 回答