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

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

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

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

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

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


这里是一个例子的研究之间的差异的优势,以面向对象和函数调用声明在C和C + +,以及允许的事实是,c90的电话的undeclared当日:

1
2
3
4
5
6
7
8
9
10
11
#include <stdio.h>

struct f { int x; };

int main() {
    f();
}

int f() {
    return printf("hello");
}

在C + +,因为这将打印一个临时的不f是创建和摧毁,但在c90 hello因为它将打印当日可以被宣布为无有。

在的情况下你是wondering关于这个名字被用来f两次,C和C + +标准explicitly允许安全和面向对象这一点,让你有想说如果你想disambiguate struct f大结构,或离开了struct你需要的功能。


为C + + VS c90,那里的至少一个的方式得到不同的行为,不是实施定义。c90不单线有评论。与一个小的照顾,我们可以安全的使用,来创建与完全不同的表达结果在c90和C + +。

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

在C + +,一切从//到最后的线是一个评论,所以这作品出来为:

1
int a = 10 + 3;

自c90线有不单是一个唯一的/* comment */评论,评论。第一个是/2initialization两部分,所以它是走出大:

1
int a = 10 / 2 + 3;

所以,一个C + +编译会给correct 13,但strictly correct 8 c90编译。当然,我只是在这里把arbitrary数——你可以使用其他数字作为你看fit。


有效的以下,C和C + +,是发现大(最有可能)的结果在不同的价值观i在C和C + +。

1
int i = sizeof('a');

看到大小的字符(A)在C / C + +为安全的解释的差异。

本文从另一个:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#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
"
, val);
    return 0;
}


c90 VS C + + 11(intVS double):

1
2
3
4
5
6
7
8
#include <stdio.h>

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

C auto意味着本地的变量。在c90这是好大omit变量或函数的类型。它defaults int大。在C + + 11 auto意味着一些完全不同的编译,它告诉infer型可变大的研究价值initialize用来从它。


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

1
2
3
4
5
6
7
8
9
10
11
12
#include <stdio.h>
int main()
{
#if true
    printf("true!
"
);
#else
    printf("false!
"
);
#endif
    return 0;
}

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


C + +标准:每11

A.用逗号performs操作员的lvalue -大- rvalue转换C,C + +,但不

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

在C + +的表达价值这将是100,这将是sizeof(char*)C。

B.在C + +的类型的enumerator是其enum。C型int enumerator是的。

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

这意味着sizeof(int)可能不是equal sizeof(E)是大的。

C. C + +函数声明与一个空的列表以params不支持。C的意思是,params空列表的数量和类型的params功能是未知的。

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


该程序在C++中打印EDCOX1×7,EDCX1为8。

1
2
3
4
5
6
7
8
9
#include <stdio.h>
#include <stdlib.h>

int main(void)
{
    int d = (int)(abs(0.6) + 0.5);
    printf("%d", d);
    return 0;
}

这是因为在C++中有EDOCX1,9的过载,所以EDCOX1,10,返回EDCOX1,11,C,它在调用EDCOX1,13,之前,由于隐式的双到int转换,返回EDCOX1,8,8。在C语言中,您必须使用fabs来与double一起工作。


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

1
2
3
4
5
#include <stdio.h>
int main() {
    printf("%d
"
, (int)sizeof !0);
}

它与C中的EDCOX1〔5〕相等,因为表达式是EDCOX1〔6〕的类型,但在C++中通常为1(虽然它不是必需的)。实际上,它们几乎总是不同的。


1
2
3
4
5
6
7
8
#include <stdio.h>

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

C,这是无所谓的sizeof(int)prints的价值在当前的系统,这通常是在大多数的系统4通常在使用的今天。

在C + +,这是一个必须打印。


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

  • sizeof(A),以及rosenfield @提到亚当.

  • //评论被用来创建隐藏的代码:

    1
    2
    3
    4
    5
    int f(int a, int b)
    {
        return a //* blah */ b
            ;
    }
  • 隐藏的东西打印出来scopes结构等,以及在你的例子。


  • 板栗,取决于一个旧的C,C + +编译,不recognizing线端的评论……

    1
    2
    3
    4
    5
    6
    ...
    int a = 4 //* */ 2
            +2;
    printf("%i
    "
    ,a);
    ...


    另一列由C + +标准:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    #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
    "
    , (int)sizeof(x));
    }


    C中的内联函数默认为外部范围,而C++中的内联函数则没有。

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

    文件1

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    #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

    1
    2
    3
    4
    5
    6
    #include <stdio.h>
    inline void fun(void)
    {
        printf("I am inline
    "
    );
    }

    此外,C++不含蓄地对待任何EDCOX1×0×Ex全局为EDCOX1×1,除非它被明确声明为EDCOX1×2Ω,不同于EDCOX1 2Ω是默认的C。


    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    struct abort
    {
        int x;
    };

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

    returns与出口零的C + +代码,或3在C.

    这个戏法能用于更多的可能是一些有趣的,但我不认为一个好的创造的方式,那将是一个大的构造palatable C.制造一个同样的例子,我试boring的构造与复制,这会让argument是安全通过,在一个相当albeit非portable时尚:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    struct exit
    {
        int x;
    };

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

        exit(code);

        return 0;
    }

    2005 refused VC + +,C + +编译时大的模式,不过,关于"出口complaining code"是如何重新定义。(我认为这是一个编译错误,除非我已经突然被人遗忘的程序如何exited)。它与代码编制的过程作为一个出口,当C虽然。


    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    #include <stdio.h>

    struct A {
        double a[32];
    };

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

    当使用C编译器编译时,该程序使用C++编译器和EDCOX1×18编译时打印EDCOX1×16(EDCOX1×17)。

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


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

    1
    2
    3
    4
    5
    6
    7
    #include <cstdio>

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

    和O2

    1
    2
    3
    4
    5
    6
    7
    #include <stdio.h>

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

    现在假设您有一个main.c和main.cpp,它们都如下所示:

    1
    2
    3
    4
    5
    6
    7
    extern void foo(int);

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

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

    1
    2
    3
    4
    5
    6
    7
    $ 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


    1
    2
    3
    4
    int main(void) {
        const int dim = 5;
        int array[dim];
    }

    这是非常奇怪的,因为它在C++和C99、C11和C17中是有效的(虽然在C11,C17中是可选的);但是在C89中无效。

    在C99 +中,它创建了一个可变长度的数组,它在普通数组上有它自己的特性,因为它具有运行时类型而不是编译时类型,而EDCOX1×0Ω不是C++中C.中的一个整数常量表达式,类型是完全静态的。

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

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

    是有效的C++,而不是C,因为可变长度数组不能有初始化器。


    这涉及到C和C++中的L值和R值。

    在C编程语言中,预增量和后增量操作符都返回值,而不是lvalues。这意味着它们不能位于=赋值运算符的左侧。这两个语句都会在C中产生编译器错误:

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

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

    1
    2
    3
    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本身。我们可以使用将这个左值赋给其他东西,它类似于下面的语句。这是左值到右值的隐式转换。

    1
    2
    int x = a;
    int x = ++a;

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

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


    空结构在C中有0个大小,C++中有1个。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    #include <stdio.h>

    typedef struct {} Foo;

    int main()
    {
        printf("%zd
    "
    , sizeof(Foo));
        return 0;
    }