为什么C ++允许我将const char分配给const char * ?!


71

令我惊讶的是,它汇编为:

const char* c_str()
{
    static const char nullchar = '\0';
    return nullchar;
}

并且在我的代码中引入了一个错误。幸运的是,我抓住了它。

这是C ++故意还是编译器错误?是否有理由主动忽略数据类型?
它可以在Visual C ++ 2010和GCC中工作,但是鉴于明显的数据类型不匹配,我不明白为什么它应该工作。(static也没有必要。)


您是否根据C ++ 03进行编译?
obataku

1
@ ta.speot.is:我认为这与CPU体系结构没有关系……
user541686

3
当然,C ++ 98确实具有编译时常量表达式的概念。
2012年

1
@Mehrdad井constexpr是++ 11-特定的C反正...但const变量是由C ++ 03标准...的§5.19.1常量表达式积分常数表达式可以只涉及文字(2.13),统计员,const使用常数表达式(8.5)初始化的整数或枚举类型的变量或静态数据成员,整数或枚举类型的非类型模板参数以及sizeof表达式。
obataku

2
@ ta.speot.is“也许指针是64位的吗? ”大小不是问题。
curiousguy 2012年

Answers:


69

如您所定义,它nullchar是一个整数常量表达式,其值为0。

C ++ 03标准将空指针常量定义为:“空指针常量是整数类型的整数常量表达式(5.19)rvalue,其值为零。” 长话短说,yournullchar是一个空指针常量,这意味着它可以隐式转换并分配给基本上任何指针。

请注意,所有这些元素都是使隐式转换正常运行所必需的。例如,如果您使用'\1'而不是'\0',或者您指定的const限定词nullchar,那么您将无法获得隐式转换-您的分配将会失败。

包含此转换是有意的,但众所周知。0为空指针常量,是从C继承的。我相当确定Bjarne和大多数C ++标准委员会(以及大多数C ++社区)将非常希望删除此特定的隐式转换,但是这样做会破坏与许多C代码的兼容性(可能接近所有C代码)。


4
可能值得补充的是,在C中,const具有值的变量0不能视为空指针常量,这意味着OP的代码在C中无效(因此,不会因任何更改而“中断”,因为它已中断从C的角度来看)。
AnT 2012年

1
@JerryCoffin:呃,但只要求的等价字面零到一个空指针,而不是一个任意常量表达式。在将类型分配给完全不相关的显式类型,为什么还要忽略该类型呢?换句话说,用正确的类型替换整个表达式,剥离类型而不是仅仅去除value的意义何在?据我所知,这不会破坏很多明智的代码...
user541686

1
@Mehrdad:照原样,它们依赖于从一个int值(0)到指针的隐式转换。为了使它在另一个方向上起作用,您必须允许从任何指针到int的隐式转换-我敢肯定这会更糟。
杰里·科芬

2
所以也许像这样:为什么允许将整数常量表达式隐式转换为空指针(而不是仅允许文字)?(请注意,这是一个假设问题)。
马那古(Managu)2012年

2
@Mehrdad:在C或C ++中没有这样的转换。在C ++中,它通过隐式转换为来工作bool。在C语言中,它通过与文字的隐式比较来工作0,即if (p)等于if (p != 0)。后者也不使用转换为int
AnT 2012年

28

这是一个古老的历史:它可以追溯到C。

nullC中没有关键字。C中的空指针常量是:

  • 整数表达式中具有0值,如00L'\0'(记住,char是整体型),(2-4/2)
  • 这样的表达浇铸到void*,如(void*)0(void*)0L(void*)'\0'(void*)(2-4/2)

NULL (不是关键字!)扩展到这样的空指针常量。

在第一个C ++设计中,仅允许将整数常量表达式用作空指针常量。最近std::nullptr_t被添加到C ++。

在C ++中,但在C中不是,const用整数常量表达式初始化的整数类型变量是整数常量表达式:

const int c = 3;
int i;

switch(i) {
case c: // valid C++
// but invalid C!
}

因此const char,使用表达式初始化的'\0'是一个空指针常量:

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 ++程序员的一些说明:

  • 对于C ++程序员称为“文字”,C使用术语“常量”。
  • 在C ++中,sizeof始终是一个编译时间常数。但是C具有可变长度的数组,因此sizeof有时不是编译时间常数。

然后,我们看到§6.3.2.3.3状态...

值为0的整数常量表达式或转换为类型的表达式 void *称为空指针常量。如果将空指针常量转换为指针类型,则保证生成的指针(称为空指针)将不等于与任何对象或函数的指针进行比较。


要了解此功能的年代久远,请参阅C99标准中相同的镜像部件...

第6.6.6节

一个整数常量表达式应具有整数型和应仅具有是积分常数,枚举常数,字符常数,操作数sizeof表达式,其结果是积分常数,和浮动是铸件的立即操作数的常量。整数常量表达式中的强制转换运算符只能将算术类型转换为整数类型,除非作为操作数的一部分。sizeof运算符的一部分。

§6.3.2.3.3

值为0的整数常量表达式或转换为类型的表达式 void *称为空指针常量。如果将空指针常量转换为指针类型,则保证生成的指针(称为空指针)将不等于与任何对象或函数的指针进行比较。


3
+1好信息,谢谢。关于您的问题:不,我认为这不是合理的语言设计,因为它显然在我的代码中引入了不必要的错误。:P
user541686 2012年

14

nullchar 是一个(编译时)常量表达式,值为0。因此,对于隐式转换为null指针来说,这是一个公平的游戏。

更详细地说:我在这里引用1996年标准草案

char是整数类型。 nullchar是const,所以它是一个(编译时)整数常量表达式,如5.19.1节所述:

5.19常数表达式[expr.const]

1在某些地方,C ++要求表达式的计算结果为整数或枚举常量...整数常量表达式可能涉及... const变量...

此外,nullchar根据第4.10.1节,计算结果为0,允许将其隐式转换为指针:

4.10指针转换[conv.ptr]

1整数类型的整数常量表达式(expr.const)的右值为0(称为空指针常量),可以转换为指针类型。

可能的一个直观原因“为什么”(就在我头顶)是未指定指针宽度,因此允许从任何大小的整数常量表达式转换为空指针。


已更新(较新的)C ++ 03标准的相关部分...根据§5.19.1...

一个积分常数表达式可以只涉及文字(2.13),统计员,const变量或常量表达式(8.5),积分或枚举类型的非类型模板参数,并初始化积分或枚举类型的静态数据成员sizeof表达式。

然后,我们来看§4.10.1...

空指针常数是整数表达式(5.19)的整数类型的计算结果为零的右值。空指针常量可以转换为指针类型。结果是该类型的空指针值,并且可以与指向对象的指针或指向函数类型的指针的每个其他值区分开。相同类型的两个空指针值应比较相等。


好吧,这显然是正在发生事情,但是(为什么)C ++允许这样做?这就是问题所在。
user541686

至少在GCC中,编译器不生成错误的唯一时间是当该值为0时。如果您写入static const char nullchar = '\x30'或任何其他值,则编译确实会失败。因此Managu是正确的:0是特例。如果要在gcc中发出警告,请-Wconversion在命令行上使用,它会在所有未明确使用强制转换的转换中发出警告。不确定MSVC。
李斯特先生,2012年

2
我也很好奇。我想知道C ++标准是否真的允许这样做,还是过早进行常量替换的副作用。
sylvain.joyeux 2012年

@ sylvain.joyeux试试看g++ -std=c98 -pedantic -W -Wall……您会看到它是标准的定义明确的部分:-)
obataku 2012年

@veer:只需提一下:我实际上是故意引用了较早的(草稿)标准,以指出此功能根本不是新功能。
Managu 2012年

11

出于与编译相同的原因进行编译

const char *p = 0; // OK

const int i = 0;
double *q = i; // OK

const short s = 0;
long *r = s; // OK

右侧的表达式具有类型intshort,而要初始化的对象是一个指针。这会让您感到惊讶吗?

在C ++语言(以及C语言)中,带值的整数常量表达式(ICE) 0具有特殊的状态(尽管ICE在C和C ++中的定义不同)。它们符合空指针常量。在指针上下文中使用它们时,它们将隐式转换为适当类型的空指针。

类型char是整数类型,int在这种情况下没有太大区别,因此const char初始化对象0也是C ++中的空指针常量(但在C中不是)。

顺便说一句,boolC ++中的类型也是整数类型,这意味着const bool由初始化的对象false也是空指针常量

const bool b = false;
float *t = b; // OK

后来针对C ++ 11的缺陷报告已更改了空指针常量的定义。校正之后,空指针常量只能是“值为零的整数文字或std :: nullptr_t类型的prvalue”。更正后,以上指针初始化在C ++ 11中不再具有良好的格式。


1
积分常量表达式(ICE) ”不要与内部编译器错误混淆;)
curiousguy 2012年

@curiousguy:我把它与H2O混淆了;)
user541686

6

它不会忽略数据类型。这不是错误。它利用了您在其中放置的const的优势,并看到它的值实际上是整数0(char是整数类型)。

整数0是有效的(按定义)空指针常量,可以将其转换为指针类型(成为空指针)。

您希望空指针的原因是具有一些“指向无处”并且可以检查的指针值(即,您可以将空指针与整数0进行比较,并且返回true)。

如果删除const,则会出现错误。如果将double放入其中(与其他许多非整数类型一样;我想例外是只能通过[转换运算符的重载]转换为const char *的类型),您将得到一个错误(甚至是w / o const)。依此类推。

整个问题是,在这种情况下,您的实现看到您正在返回一个空的ptr常量;您可以将其转换为指针类型。


5

看来,这个问题的许多真正答案已经在评论中结束了。总结一下:

  • C ++标准允许const将整数类型的变量视为“整数常量表达式”。为什么?很有可能绕过C仅允许宏和枚举保留整数常量表达式的位置的问题。

  • 至少可以追溯到C89,值为0的整数常量表达式可以隐式转换为(任何类型的)空指针。这也是C代码,其中经常使用的NULL是经常#define“d作为(void*)0

  • 回到K&R,文字值0已用于表示空指针。该约定在各处使用,其代码如下:

    if ((ptr=malloc(...)) {...} else {/* error */}
    

实际上,如果我记得正确的话,那也是出于历史原因。在某些时候,已经有太多代码将(由文字定义)char字符串称为char *,因此在const引入时允许有问题的赋值是合理的。我不确定是否可以在K&R本身中阅读此通知。
mlvljr 2012年

2

有一个自动演员表。如果运行良好,请执行以下操作:

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


1
有自动转换”没有“自动转换”之类的东西。它是隐式转换,还是强制转换(显式转换)。
curiousguy 2012年

2

我认为这可能是因为空字符在类型之间很常见。您正在做的是在返回空字符时设置空指针。如果使用任何其他字符,则此操作将失败,因为您不是将字符的地址传递给指针,而是将字符的值传递给指针。Null是有效的指针和字符值,因此可以将空字符设置为指针。

简而言之,任何类型的null都可以用来设置空值,无论它是数组,指针还是变量。

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.