C 和 C++ 有很多不同之处,并非所有有效的 C 代码都是有效的 C++ 代码。 (“有效”是指具有已定义行为的标准代码,即不是特定于实现/未定义/等。)
当使用每种语言的标准编译器编译时,在 C 和 C++ 中都有效的一段代码是否会产生不同的行为?
为了使它成为一个合理/有用的比较(我试图学习一些实际有用的东西,而不是试图在问题中找到明显的漏洞),让我们假设:
没有与预处理器相关的东西(这意味着没有 #ifdef __cplusplus、pragma 等的 hack)
任何实现定义在两种语言中都是相同的(例如数字限制等)
我们正在比较每个标准的最新版本(例如,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。当然,我在这里选择了任意数字——您可以使用您认为合适的其他数字。
2
,它也会读取为有效的 10 / + 3
(一元 +)。
以下在 C 和 C++ 中有效,将(很可能)在 C 和 C++ 中导致 i
中的不同值:
int i = sizeof('a');
有关差异的说明,请参见 Size of character ('a') in C/C++。
来自 this article 的另一个:
#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;
}
struct
。
struct sz { int i[2];};
表示 C 和 C++ 必须 产生不同的值。 (而 sizeof(int) == 1 的 DSP,可以 产生相同的值)。
C90 与 C++11(int
与 double
):
#include <stdio.h>
int main()
{
auto j = 1.5;
printf("%d", (int)sizeof(j));
return 0;
}
在 C 中 auto
表示局部变量。在 C90 中,可以省略变量或函数类型。默认为 int
。在 C++11 中 auto
意味着完全不同的东西,它告诉编译器从用于初始化它的值推断变量的类型。
int
。这很聪明! +1
int
。
int
。尽管如此,在现实世界中,存在大量遗留代码,而市场领导者仍未实现 C99 并且无意这样做,因此谈论“过时的 C 版本”是荒谬的。
另一个我还没有看到的例子,这个例子突出了预处理器的区别:
#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。
#define true false
ಠ_ಠ
根据 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
sizeof(char*)
可能是 100,在这种情况下,第一个示例将在 C 和 C++ 中产生相同的可观察行为(即,虽然获得 s
的方法不同,但 s
最终会是 100)。 OP提到这种类型的实现定义的行为很好,因为他只是想避免语言律师的回答,所以第一个是他的例外。但无论如何,第二个都很好。
char arr[sizeof(char*)+1]; int s = sizeof(0, arr);
void *arr[100]
。在这种情况下,元素与指向同一元素的指针大小相同,因此只要有 2 个或更多元素,数组就必须大于其第一个元素的地址。
该程序在 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
。
stdlib.h
仅定义 abs(int)
和 abs(long)
;版本 abs(double)
由 math.h
声明。所以这个程序可能仍然调用 abs(int)
版本。 stdlib.h
是否也会导致包含 math.h
是一个实现细节。 (我认为如果调用 abs(double)
将是一个错误,但不包括 math.h
的其他方面)。
<math.h>
还包括额外的重载;实际上,除非使用 <cmath>
形式,否则所有主要编译器都不包含这些重载。
#include <stdio.h>
int main(void)
{
printf("%d\n", (int)sizeof('a'));
return 0;
}
在 C 中,这会打印当前系统上 sizeof(int)
的任何值,在当今常用的大多数系统中通常是 4
。
在 C++ 中,这必须打印 1。
%d
不是 size_t
的正确格式说明符。
另一个 sizeof
陷阱:布尔表达式。
#include <stdio.h>
int main() {
printf("%d\n", (int)sizeof !0);
}
它在 C 中等于 sizeof(int)
,因为表达式是 int
类型,但在 C++ 中通常为 1(尽管不是必须的)。在实践中,它们几乎总是不同的。
!
应该足够一个 bool
。
sizeof(0)
在 C 和 C++ 中都是 4
,因为 0
是整数右值。 sizeof(!0)
在 C 中是 4
,在 C++ 中是 1
。逻辑 NOT 对 bool 类型的操作数进行操作。如果 int 值为 0
,则它被隐式转换为 false
(一个布尔右值),然后它被翻转,产生 true
。 true
和 false
都是 C++ 中的布尔右值,sizeof(bool)
是 1
。但是在 C 中,!0
的计算结果为 1
,它是 int 类型的右值。 C 编程语言默认没有 bool 数据类型。
一个依赖 C 编译器的老栗子,无法识别 C++ 行尾注释......
...
int a = 4 //* */ 2
+2;
printf("%i\n",a);
...
C++ 编程语言(第 3 版)给出了三个示例:
sizeof('a'),正如@Adam Rosenfield 提到的那样; // 用于创建隐藏代码的注释: int f(int a, int b) { return a //* blah */ b ;结构等将东西隐藏在范围之外,如您的示例所示。
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));
}
x
。我以为你说的是“数组 a
”。
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
是默认值不同。
extern
的不同行为是什么?
struct fun
与 fn
),与函数是否内联无关。如果您删除 inline
限定符,结果是相同的。
inline
,但在 C99 中,如果没有范围内的原型,则不能调用 fun()
。所以我认为这个答案只适用于 GNU 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 退出。
exit(code)
显然是 exit
类型的变量 code
的有效声明。 (请参阅“最令人烦恼的解析”,这是一个不同但相似的问题)。
#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++ 编译器编译时打印 128
(32 * sizeof(double)
),在使用 C 编译器编译时打印 4
。
这是因为 C 没有范围解析的概念。在 C 中,包含在其他结构中的结构被放入外部结构的范围内。
32*sizeof(double)
而不是 32 :))
size_t
with %d
之前获得 UB
不要忘记 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.c 和 main.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
foo
的多重定义)。没有单独的“全局命名空间”。
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 和 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++.
空结构在 C 中的大小为 0,在 C++ 中为 1:
#include <stdio.h>
typedef struct {} Foo;
int main()
{
printf("%zd\n", sizeof(Foo));
return 0;
}