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


664

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

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

为了使之合理/有用(我试图学习一些实用的东西,而不是试图在问题中发现明显的漏洞),让我们假设:

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

11
顺便说一句,同时使用C和C ++的方言进行编程可能会很有用。我在过去和一个目前的项目中都做到了这一点:TXR语言。有趣的是,Lua语言的开发人员也做了同样的事情,他们称这种方言为“ Clean C”。您可以从C ++编译器中获得更好的编译时检查和可能的其他有用诊断信息,同时保留C的可移植性。
卡兹(Kaz)2012年

9
我将较早的问题合并到该问题中,因为它具有更多的观点和赞成的答案。这仍然是一个非建设性问题的示例,但这是一个非常临界的问题,因为是的,它确实向SO用户提供了一些知识。我关闭它不是有建设性的,只是为了反映合并之前问题的状态。随时不同意并重新打开。
George Stocker 2012年

13
我认为可以用“是”客观地回答重新投票,然后举一个例子(如下所述)。我认为这是建设性的,因为人们可以从中学习相关行为。
Anders Abel

6
@AndersAbel答案都是正确的,纯正的数字明确表明它仍然是一个成问题的问题。如果没有列表,您根本不可能问这个问题。
dmckee ---前主持人小猫,

2
@dmckee对于它的价值,我同意你的看法。但是,C ++标记人员是...我们应该说... feisty
乔治·斯托克

Answers:


397

以下内容(在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;
}

8
绝对没想到这个!我希望有一些更具戏剧性的东西,但是仍然有用,谢谢。:) +1
user541686 2012年

17
+1第二个示例是一个很好的例子,因为C ++ struct在结构名称之前不需要。
塞斯·卡内基

1
@Andrey我不久前也有同样的想法,并对其进行了测试,并且它在没有std的GCC 4.7.1上工作,这与我的预期相反。那是GCC中的错误吗?
塞斯·卡内基

3
@SethCarnegie:不合格的程序不必失败,但是也不能保证它也能运行。
Andrey Vihrov

3
struct sz { int i[2];};意味着C和C ++ 必须产生不同的值。(而sizeof(int)== 1的DSP 可能产生相同的值)。
马丁·邦纳

464

这是一个利用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要使用的函数。


7
严格来讲,在C语言下不会编译,因为“ int f()”的声明位于“ int main()”的定义之后:)
Sogartar,2012年

15
@Sogartar,真的吗?codepad.org/STSQlUhh C99编译器会向您发出警告,但仍然可以让您对其进行编译。
jrajav 2012年

22
允许隐式声明C函数中的@Sogartar。
Alex B

11
@AlexB不在C99和C11中。

6
@jrajav,那不是C99编译器。C99编译器将未声明的标识符检测为语法错误。不这样做的编译器是C89编译器,或者是标准或其他不合格的编译器。

430

对于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。当然,我在这里选择了任意数字-您可以视需要使用其他数字。


34
哇,这真令人兴奋!在所有可能的事情中,我都不会想到评论可以用来改变行为哈哈。+1
user541686

89
即使没有2,它也将被视为10 / + 3有效(一元+)。
贝诺瓦(Benoit)

12
现在,为了好玩,对其进行修改,以使C和C ++都可以计算不同的算术表达式,并求和为同一结果。
瑞安·汤普森

21
@RyanThompson琐碎的。s /
2/1

4
@Mehrdad我是否错误或注释与预处理程序有关?因此,应将它们排除在问题之外,以作为可能的答案!;-)
Ale

179

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意味着完全不同的东西,它告诉编译器从用于初始化变量的值推断出变量的类型。


10
C90有auto
塞斯·卡内基

22
@SethCarnegie:是的,这是一个存储类;当您忽略它时,默认情况下会发生这种情况,因此没有人使用它,并且他们更改了它的含义。我认为这是int默认设置。这很聪明!+1
user541686

5
C11没有隐式- int
R .. GitHub停止帮助ICE

23
@KeithThompson啊,我想你的意思是推断的int。尽管如此,在现实世界中,仍然有大量的遗留代码,而市场领导者仍未实施C99,也无意这样做,因此谈论“过时的C版本”是荒谬的。
Jim Balter 2012年

11
“每个变量都必须具有显式的存储类。您真正的上级管理人员。”
btown 2012年

120

我还没有提到的另一个示例,该示例突出了预处理程序的不同之处:

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


2
有趣。有人知道这一变化背后的理由吗?
2014年

3
因为“ true”是关键字/有效值,所以它像任何“ true value”(如任何正整数)一样被评估为true。您仍然可以在C ++中也执行#define true false来打印“ false”;)
CoffeDeveloper 2014年

22
#define true false ಠ_ಠ–
Bryan Boettcher

2
@DarioOO不会在UB中重新定义结果吗?
罗斯兰

3
@DarioOO:是的,您错了。不允许重新定义关键字,惩罚留给命运(UB)。预处理器是独立的编译阶段,无法承受。
Deduplicator 2015年

108

根据C ++ 11标准:

一个。逗号运算符在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

第一个也是实现定义的,就像Alexey一样。但是+1。
塞斯·卡内基

1
@Seth,以上所有材料直接取自C ++ 11标准的附件C.1。
Kirill Kobelev 2012年

是的,但是它仍然是实现定义的。sizeof(char*)可能是100,在这种情况下,第一个示例将在C和C ++中产生相同的可观察到的行为(即,尽管获取方法s会有所不同,s但最终将为100)。OP提到,这种类型的实现定义的行为很好,因为他只是想避免使用语言律师的答案,因此第一个可以通过他的例外来解决。但是无论如何,第二个还是不错的。
塞斯·卡内基

5
有一个简单的解决方法–只需将示例更改为:char arr[sizeof(char*)+1]; int s = sizeof(0, arr);
Mankarse 2012年

5
为了避免实现定义的差异,您还可以使用void *arr[100]。在这种情况下,一个元素的大小与指向同一元素的指针的大小相同,因此,只要有2个或更多元素,则数组必须大于其第一个元素的地址。
finnw 2012年

53

该程序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;
}

这是因为有double abs(double)在C ++中的过载,所以abs(0.6)返回0.6而在C中,它返回0因为调用之前的隐式双到-INT转换int abs(int)。在C中,您必须使用fabsdouble


5
不得不调试其他人的代码与此问题。哦,我多么喜欢它。无论如何,您的程序也在C ++中打印0。C ++必须使用标头“ cmath”(参见比较)第一个返回0 ideone.com/0tQB2G第二个返回1 ideone.com/SLeANo
CoffeDeveloper 2014年

很高兴听到我不是唯一通过调试发现这种差异的人。刚刚在VS2013中进行了测试,如果扩展名为.cpp,则仅包含此内容的文件为空将输出1,如果扩展名为.c,则将输出0。看起来<math.h>包含在VS中。
Pavel Chikulaev 2014年

就像在VS C ++中一样,<math.h>在全局命名空间中包含了C ++内容,而对于GCC则不是。但是不确定哪个是标准行为。
Pavel Chikulaev 2014年

2
这个特定的代码示例与实现有关: stdlib.h仅定义abs(int)abs(long); 版本abs(double)由声明math.h。因此,该程序仍可以调用abs(int)版本。是否stdlib.hmath.h包括在内是实现细节。(我认为如果abs(double)被调用将是一个错误,但是math.h不包括其他规格)。
MM

1
第二个问题是,尽管C ++标准确实说包括在内<math.h>还包括附加的重载;实际上,事实证明,除非使用了表格,否则所有主要的编译器都不会包含那些重载<cmath>
MM

38
#include <stdio.h>

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

在C语言中,此命令将打印sizeof(int)当前系统上的值,无论该值在当前通常4使用的大多数系统中都是如此。

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


3
是的,我实际上对这个技巧很熟悉,因为'c'在C语言中是int,在C ++中是char,但是在此处列出它仍然很不错。
肖恩

9
这将使一个有趣的面试问题-特别是对于那些把C / C ++的专家对他们的简历的人
马丁·贝克特

2
虽然有点不满意。sizeof的全部目的是使您不必确切知道类型的大小。
Dana the Sane,

13
在C中,值是实现定义的值,并且1是可能。(在C ++中,它必须按规定打印1。)
Windows程序员,

3
实际上,在两种情况下它都具有未定义的行为。%d不是正确的格式说明符size_t
R .. GitHub停止帮助ICE 2014年

37

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

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

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


6
一个就!应该足够了bool
阿列克谢·弗伦兹

4
!! 是int到boolean转换运算符:)
EvilTeach

1
sizeof(0)4在C和C ++,因为0是整数右值。sizeof(!0)4在C和1C ++编写。逻辑NOT对布尔类型的操作数进行运算。如果将int值0隐式转换为false(布尔值),则将其翻转,得到true。都truefalse在C ++布尔右值和sizeof(bool)1。但是在C中,!0计算结果为1,这是int类型的右值。默认情况下,C编程语言没有bool数据类型。
星系

26

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

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

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

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



21

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)); 
}

所以你得到填充差异?
v.oddou

啊,对不起,我明白了,x顶部还有另一个。我以为你说“数组a”。
v.oddou

20

C语言中的内联函数默认为外部作用域,而C ++语言中则没有。

在GNU C的情况下,将以下两个文件编译在一起将显示“ I in 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 成为默认值,否则C ++会隐式将任何const全局值视为static未明确声明的全局值。externextern


我真的不这么认为。可能您错过了重点。这与仅用于使代码有效的c ++的struct st的定义无关。关键是要强调c和c ++中内联函数的不同行为。同样适用于外部。在任何解决方案中都没有讨论这些。
2012年

2
内联函数的不同行为extern是什么?
塞斯·卡内基

它写得很清楚。“ c中的内联函数默认为外部作用域,而c ++中的内联函数则不在(代码显示)。同样,C ++隐式将任何const全局视为文件作用域,除非明确声明为extern,这与C的默认值为extern不同。可以为此创建示例”。我很困惑-这是无法理解的吗?
2012年

12
@fayyazkl所示的行为仅是由于查找(struct funvs fn)的差异,与函数是否为内联无关。如果删除inline限定符,则结果相同。
Alex B

3
在ISO C中,该程序的格式不正确:inline直到C99才添加,但是fun()在范围内没有原型时可能无法调用C99 。所以我想这个答案只适用于GNU C.
MM

16
struct abort
{
    int x;
};

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

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

这个技巧可能可以用来做一些更有趣的事情,但是我想不出一种创建对C语言友好的构造函数的好方法。我尝试使用copy构造函数制作一个类似的乏味示例,该示例会让参数通过,尽管以一种非便携式的方式:

struct exit
{
    int x;
};

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

    exit(code);

    return 0;
}

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


不幸的是,使用出口的第二个示例无法在gcc或g ++上编译。不过,这是个好主意。
肖恩

1
exit(code)显然code是类型为的变量的有效声明exit。(请参阅“最令人烦恼的分析”,这是一个不同但相似的问题)。
user253751 2015年

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编译器编译时,该程序都会打印12832 * sizeof(double)4

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


这个很有趣!(我认为您的意思32*sizeof(double)虽然不是32):)
user1354557 '16

3
请注意,通过使用size_t%d
phuclv

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

您是指链接规范?
user541686 2014年

名字改头换面。C ++名称有前缀和后缀而C不
CoffeDeveloper

名称修饰不是C ++规范的一部分。在C中被禁止吗?
2015年

5
这是未定义的行为(的多个定义foo)。没有单独的“全局名称空间”。
MM

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,因为可变长度数组不能具有初始化程序。


0

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

在C编程语言中,前递增和后递增运算符均返回rvalue,而不是lvalue。这意味着它们不能在=赋值运算符的左侧。这两个语句都将在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 ++中,预增量运算符返回一个 lvalue,而后增量运算符返回一个rvalue。这意味着可以将带有预增量运算符的表达式放在=赋值运算符的左侧!

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++.

3
这里没有“在C中有效”。
o11c

0

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

#include <stdio.h>

typedef struct {} Foo;

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

1
不,区别在于C 除了作为编译器扩展之外,没有空结构,即,此代码与“在C和C ++中均有效”不匹配
Antti Haapala
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.