【问题标题】:Why is C++ allowing me to assign a const char to a const char *?​!为什么 C++ 允许我将 const char 分配给 const char *?!
【发布时间】:2023-04-02 09:41:01
【问题描述】:

令我惊讶的是,这编译:

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

它在我的代码中引入了一个错误。谢天谢地,我抓住了它。

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

【问题讨论】:

  • 你是按照C++03编译的吗?
  • @ta.speot.is:我认为这与 CPU 架构无关......
  • 当然 C++98 确实有编译时常量表达式的概念。
  • @Mehrdad @Mehrdad constexpr 无论如何都是 C++11 特定的......但 const 变量是 C++03 标准第 5.19.1 节的常量表达式...... integral constant-expression 只能涉及字面量 (2.13)、枚举器、const 变量或整数或枚举类型的静态数据成员,用常量表达式 (8.5)、非类型模板初始化整数或枚举类型的参数,以及sizeof 表达式。
  • @ta.speot.is "也许指针是 64 位的?" 大小不是问题。

标签: c++ implicit-conversion type-safety


【解决方案1】:

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

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

请注意,所有这些元素都是隐式转换工作所必需的。例如,如果您使用了'\1' 而不是'\0',或者如果您没有const 指定了nullchar 限定符,您将不会获得隐式转换——您的分配会失败。

包含此转换是有意的,但众所周知是不可取的。 0 作为一个空指针常量是从 C 继承的。我很确定 Bjarne 和 C++ 标准委员会的其他大多数成员(以及一般 C++ 社区的大多数成员)都非常喜欢删除这个特定的隐式转换,但是这样做会破坏与许多 C 代码的兼容性(可能接近全部)。

【讨论】:

  • 可能值得补充的是,在 C 中,具有值 0const 变量不能作为空指针常量,这意味着 OP 的代码在 C 中无效(因此不会被任何更改“破坏”,因为从 C 的角度来看它已经被破坏了)。
  • @JerryCoffin:呃,但这只需要 literal 零与空指针等价,而不是任意常量表达式。为什么忽略类型 after 它被分配给明确键入为完全不相关的东西?换句话说,用正确的类型替换整个表达式,剥离类型,而不仅仅是,有什么意义呢?据我所知,这不会破坏太多合理的代码......
  • @Mehrdad:按原样,它们依赖于从一个 int 值 (0) 到指针的隐式转换。为了让它在另一个方向上工作,你必须允许从 any 指针到 int 的隐式转换——我敢肯定这会更糟。
  • 所以可能是这样的:为什么允许整数常量表达式隐式转换为空指针(而不是只允许文字)? (请注意,这是一个假设问题)。
  • @Mehrdad:在 C 或 C++ 中都没有这样的转换。在 C++ 中,它通过隐式转换为bool 来工作。在 C 中,它通过与文字 0 的隐式比较来工作,即 if (p) 等价于 if (p != 0)。后者也不使用转换为int
【解决方案2】:

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

C 中没有 null 关键字。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!
}

所以用表达式'\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++ 程序员的一些说明:

  • C 使用术语“常量”来表示 C++ 程序员所称的“文字”。
  • 在 C++ 中,sizeof 始终是编译时间常数;但是 C 有可变长度数组,所以 sizeof 有时不是编译时间常数。

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

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


要查看此功能的历史,请查看C99 standard 中相同的镜像部分...

§6.6.6

整数常量表达式应该是整数类型,并且只能有整数常量、枚举常量、字符常量、sizeof表达式的操作数,其结果是整数常量,以及作为强制转换的直接操作数的浮点常量。整数常量表达式中的强制转换运算符只能将算术类型转换为整数类型,但作为 sizeof 运算符的操作数的一部分除外。

§6.3.2.3.3

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

【讨论】:

  • +1 很棒的信息,谢谢。关于您的问题:不,我认为这不是合理的语言设计,因为它显然在我的代码中引入了一个不必要的错误。 :P
【解决方案3】:

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

更详细地说:我在这里引用1996 draft standard

char 是一个整数类型。 nullchar 是 const,因此它是(编译时)整数常量表达式,根据第 5.19.1 节:

5.19 常量表达式 [expr.const]

1 在几个地方,C++ 需要计算结果为整数的表达式 gral 或枚举常量 ... 一个整数常量表达式可以涉及 ... 常量变量 ...

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

4.10 指针转换 [conv.ptr]

1 整数类型的整数常量表达式 (expr.const) 右值 计算结果为零(称为空指针常量)可以是 转换为指针类型。

也许“为什么”这可能是允许的直观原因(就在我的脑海中)是没有指定指针宽度,因此允许从任何大小的整数常量表达式转换为空指针。


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

整型常量表达式只能涉及文字 (2.13)、枚举器、const 变量或使用常量表达式 (8.5) 初始化的整型或枚举类型的静态数据成员、非类型模板整数或枚举类型的参数,以及sizeof 表达式。

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

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

【讨论】:

  • 嗯,这显然是正在发生的,但是(为什么)它是 C++ 允许的?就是这个问题。
  • 至少在 GCC 中,编译器不产生错误的唯一时间是值为 0 时。如果你写 static const char nullchar = '\x30' 或任何其他值,编译确实会失败。所以 Managu 是对的:0 是一个特例。如果您想在 gcc 中出现警告,请在命令行上使用 -Wconversion,它会在您未明确使用强制转换的所有转换中发出警告。不确定 MSVC。
  • 我也很好奇。我想知道 C++ 标准是否真的允许这样做,或者过早进行常量替换的副作用。
  • @sylvain.joyeux 试试 g++ -std=c98 -pedantic -W -Wall... 你会发现它是标准中定义明确的部分 :-)
  • @veer:顺便提一下:我实际上是故意引用了早期的(草案)标准,以指出这个功能根本不是新的。
【解决方案4】:

它的编译原因与它编译的原因完全相同

const char *p = 0; // OK

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

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

右边的表达式有intshort 类型,而被初始化的对象是一个指针。这让你感到惊讶吗?

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

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

顺便说一句,C++中的类型bool也是整数类型,也就是说false初始化的const bool对象也是一个空指针常量

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

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

【讨论】:

  • "整数常量表达式 (ICE)" 不会与内部编译器错误混淆 ;)
  • @curiousguy:我把它和 H₂O 混淆了;)
【解决方案5】:

它没有忽略数据类型。这不是一个错误。它利用了你放在那里的 const 并看到它的值实际上是一个整数 0(char 是一个整数类型)。

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

你想要空指针的原因是有一些指针值“指向无处”并且可以检查(即你可以将空指针与整数0进行比较,你会得到真实的回报) .

如果你去掉 const,你会得到一个错误。如果你把 double 放在那里(与许多其他非整数类型一样;我想例外只是可以转换为 const char* [通过转换运算符的重载]的类型),你会得到一个错误(即使 w/o常量)。以此类推。

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

【讨论】:

    【解决方案6】:

    似乎这个问题的很多真正答案都在 cmets 中结束了。总结一下:

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

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

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

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

    【讨论】:

    • 其实,如果我没记错的话,那也是历史原因;在某些时候,很多代码已经将(由文字定义的)char 字符串引用为char *,因此当引入const 时,允许相关分配被认为是合理的。我是否可以在 K&R 本身中阅读此通知,不确定。
    【解决方案7】:

    有一个自动投射。如果你很好地运行这个程序:

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

    【讨论】:

    • 有一个自动投射”没有“自动投射”之类的东西。要么是隐式转换,要么是强制转换(显式转换)。
    【解决方案8】:

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

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

    【讨论】:

      猜你喜欢
      • 2021-11-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2010-09-09
      • 2014-07-17
      • 2016-08-19
      相关资源
      最近更新 更多