令我惊讶的是,这段代码可以编译通过:
const char* c_str()
{
static const char nullchar = '\0';
return nullchar;
}
出现了一个bug,我很庆幸及时发现了它。
这是C++的意图还是编译器的bug?为什么会故意忽略数据类型?
在Visual C++ 2010和GCC中可以工作,但考虑到明显的数据类型不匹配,我不明白为什么它能够工作。(static
也不是必需的。)
令我惊讶的是,这段代码可以编译通过:
const char* c_str()
{
static const char nullchar = '\0';
return nullchar;
}
出现了一个bug,我很庆幸及时发现了它。
这是C++的意图还是编译器的bug?为什么会故意忽略数据类型?
在Visual C++ 2010和GCC中可以工作,但考虑到明显的数据类型不匹配,我不明白为什么它能够工作。(static
也不是必需的。)
根据您的定义,nullchar
是一个整数常量表达式,其值为0。
C++03标准将空指针常量定义为:“空指针常量是一个整数类型的积分常数表达式(5.19),其求值结果为零。” 简而言之,您的nullchar
是空指针常量,这意味着它可以被隐式转换并赋值给任何指针。
注意,所有这些元素都需要才能使该隐式转换起作用。例如,如果您使用的是'\1'
而不是'\0'
,或者如果您没有为nullchar
指定const
限定符,则不会获得隐式转换,您的赋值将失败。
包含此转换是有意的,但广泛认为是不可取的。 0作为空指针常量是从C继承而来的。我相当确定Bjarne以及大多数C++标准委员会成员(和C++社区的大多数人)都非常希望删除这种特定的隐式转换,但这样做将破坏与大量C代码(可能接近全部)的兼容性。
const
变量并不符合空指针常量的要求,这意味着原作者的代码在C语言中无效(因此无论怎样更改都无法修复,因为从C语言的角度来看,它已经是有问题的了)。 - AnT stands with Russiabool
来工作。在C中,它通过将指针隐式比较为字面量0
来工作,即if (p)
等同于if (p != 0)
。后者也不使用转换为int
。 - AnT stands with Russia#define
),2)枚举。宏存在众所周知的严重问题。枚举除了int
类型之外不能有任何其他类型。这个问题必须得到解决。为了解决这个问题,C++将常量的概念扩展到包括const
对象。const
对象具有类型和作用域。 - AnT stands with Russiaif (p != 0)
或if ((p != 0) != 0)
或if (((p != 0) != 0) != 0)
等方式替换掉if (p)
。这些变体都是等效的。但编译器不一定会遵循这条路径。 - AnT stands with Russia这是一个古老的历史:它可以追溯到C语言。
C语言中没有 null
这个关键字。在C语言中,空指针常量可能是以下两种情况之一:
0
, 0L
, '\0'
(请记住 char
是一种整型类型),(2-4/2)
void*
,例如 (void*)0
, (void*)0L
, (void*)'\0'
, (void*)(2-4/2)
NULL
宏定义(不是关键字!)会扩展成这样的空指针常量。
在最初的C++设计中,只允许使用整型常量表达式作为空指针常量。近年来,C++ 中添加了 std::nullptr_t
。
在C++中,但不是在C语言中,整型类型的 const
变量如果使用整型常量表达式进行初始化,则是一个整型常量表达式:
const int c = 3;
int i;
switch(i) {
case c: // valid C++
// but invalid C!
}
因此,使用表达式'\0'
初始化的 const char
是一个空指针常量:
int zero() { return 0; }
void foo() {
const char k0 = '\0',
k1 = 1,
c = zero();
int *pi;
pi = k0; // OK (constant expression, value 0)
pi = k1; // error (value 1)
pi = c; // error (not a constant expression)
}
你认为这不是良好的语言设计吗?
更新以包含C99标准的相关部分... 根据§6.6.6...
一个整数常量表达式应该具有整数类型,并且只能具有操作对象,这些操作对象是整数常量、枚举常量、字符常量、其结果为整数常量的
sizeof
表达式和作为强制转换操作数的浮点常量,这些强制转换运算符在整数常量表达式中只能将算术类型转换为整数类型,除非它们是sizeof
运算符的操作数的一部分。
一些C++专属程序员的澄清:
sizeof
始终是编译时常量;但是C有可变长度数组,因此sizeof
有时不是编译时常量。然后,我们看到§6.3.2.3.3说明...
值为0的整数常量表达式或将此类表达式强制转换为类型
void *
的表达式称为空指针常量。 如果将null指针常量转换为指针类型,则所得到的指针,称为空指针,保证不等于任何对象或函数的指针。
为了看到这个功能有多古老,请参见C99标准中相同的镜像部分...
§6.6.6
一个整数常量表达式应该具有整数类型,并且只能具有操作对象,这些操作对象是整数常量、枚举常量、字符常量、其结果为整数常量的
sizeof
表达式和作为强制转换操作数的浮点常量,这些强制转换运算符在整数常量表达式中只能将算术类型转换为整数类型,除非它们是sizeof
运算符的操作数的一部分。
§6.3.2.3.3
值为0的整数常量表达式或将此类表达式强制转换为类型
void *
的表达式称为空指针常量。 如果将null指针常量转换为指针类型,则所得到的指针,称为空指针,保证不等于任何对象或函数的指针。
nullchar
是一个(编译时的)常量表达式,值为0。因此它可以隐式转换为空指针。char
是一种整数类型。nullchar
是const类型,因此它是一个(编译时的)整数常量表达式,根据第5.19.1节:
此外,5.19 常量表达式 [expr.const]
1 在多个场合下,C++要求表达式评估为整数或枚举常量...一个整数常量表达式可以涉及 ... const变量...
nullchar
评估为0,允许将其隐式转换为指针,根据第4.10.1节:
4.10 指针转换 [conv.ptr]
1 整数类型的整型常量表达式(expr.const)右值,其计算结果为零(称为空指针常量),可以转换为指针类型。
也许一个直观的原因"为什么"允许这样做(仅凭直觉)是指针宽度未指定,因此允许从任何大小的整型常量表达式转换为空指针。
根据较新的C++03标准中相关的部分更新...... 根据§5.19.1......
整数常量表达式只能涉及文本(2.13),枚举类型,使用常量表达式初始化的整型或枚举类型的
const
变量或静态数据成员,整型或枚举类型的非类型模板参数以及sizeof
表达式。
然后,我们看看§4.10.1......
空指针常量是一个整数类型的常量表达式(5.19)右值,其求值结果为零。空指针常量可以转换为指针类型;结果是该类型的空指针值,并且与指向对象或函数类型的每个其他指针值都不同。相同类型的两个空指针值应该相等。
static const char nullchar = '\x30'
或任何其他值,则编译将失败。因此,Managu是正确的:0是一个特殊情况。如果您想在gcc中获得警告,请在命令行上使用-Wconversion
,它会在所有转换(您没有明确使用强制转换的情况)上发出警告。不确定MSVC是否适用。 - Mr Lister这段代码能编译通过的原因与之前代码相同
const char *p = 0; // OK
const int i = 0;
double *q = i; // OK
const short s = 0;
long *r = s; // OK
const bool b = false;
float *t = b; // OK
一份针对C++11的缺陷报告改变了空指针常量的定义。在修正后,空指针常量只能是“值为零的整数字面量或类型为std::nullptr_t的prvalue”。在修正后,上述指针初始化在C++11中不再是良构的。
似乎这个问题的真正答案已经在评论中得到了回答。总结如下:
The C++ standard allows const
variables of integral type to be considered "integral constant expressions." Why? Quite possibly to bypass the issue that C only allows macros and enums to hold the place of integral constant expression.
Going (at least) as far back as C89, an integral constant expression with value 0 is implicitly convertible to (any type of) null pointer. And this is used often in C code, where NULL
is quite often #define
'd as (void*)0
.
Going back to K&R, the literal value 0
has been used to represent null pointers. This convention is used all over the place, with such code as:
if ((ptr=malloc(...)) {...} else {/* error */}
char *
,因此当引入const
时,允许进行相关赋值被认为是合理的。我甚至不确定是否可以在K&R本身中阅读到这个通知。 - mlvljr我认为问题在于空字符在类型之间是通用的。你所做的是在返回空字符时设置一个空指针。如果使用任何其他字符,这将失败,因为你没有将字符的地址传递给指针,而是将字符的值传递给了指针。空值是一个有效的指针和字符值,因此可以将空字符设置为指针。
简而言之,无论是数组、指针还是变量,都可以使用null来设置空值。
这里有一个自动转换功能。如果您成功运行此程序:
#include <stdio.h>
const char* c_str()
{
static const char nullchar = '\0';
return nullchar;
}
int main()
{
printf("%d" , sizeof(c_str()));
return 0;
}
在我的电脑上,输出将为4,大小与指针相同。
编译器会自动进行类型转换。请注意,至少gcc会发出警告(我不知道VS的情况)。
constexpr
本来就是C++11特有的...但是根据C++03标准的§5.19.1,const
变量是一个常量表达式...一个整数常量表达式只能包含字面值(2.13),枚举器,用常量表达式初始化的整数或枚举类型的const
变量或静态数据成员(8.5),整数或枚举类型的非类型模板参数以及sizeof
表达式。 - obataku