为啥使用三元运算符进行数组初始化是非法的?

Posted

技术标签:

【中文标题】为啥使用三元运算符进行数组初始化是非法的?【英文标题】:Why is array initialization with ternary operator illegal?为什么使用三元运算符进行数组初始化是非法的? 【发布时间】:2019-06-22 23:17:59 【问题描述】:

C 让我可以经常互换使用char 指针和数组,以至于我经常认为它们是完全可以互换的。但是下面的代码证明这不是真的。谁能解释一下为什么在下面的代码中使用三元运算符初始化const char d[] 是非法的?

/* main.c */
#include <stdio.h>

int main()

  const char* a = "lorem";
  const char b[] = "ipsum";
  int* p;
  const char* c = ( *p ? "dolor" : "sit" );
  const char d[] = ( *p ? "amet" : "consectetur" ); // Why am I an error?
  return 0;

编译:

> gcc -g main.c 
main.c: In function \u2018main\u2019:
main.c:10:20: error: invalid initializer
   const char d[] = ( *p ? "amet" : "consectetur" ); // Why am I an error?

相关问题:如果我的术语在这里不准确:描述const char d[] 的正确术语是什么?它是一个数组吗?变长数组?还有什么?它不被视为指针 - 是吗?

编辑:我相信Array initialization with a ternary operator?没有回答这个问题

RE:引用的问题,我相信前提略有不同。例如。接受的答案解释说 1, 2 ;(或 'a', 'b' );)不是有效的C表达式,我已经知道并接受。但是"amet";"consectetur"; 是有效的C 表达式。

【问题讨论】:

数组和指针几乎完全不相关。但是,数组可以隐式转换为指针。就是这样。 Array initialization with a ternary operator?的可能重复 @melpomene - 对:数组是一个固定的内存块,可能保存用户初始化的内容,而指针实际上是一个整数,其值恰好是一个内存位置。这是完全正确的,还是我错过了一些微妙之处? @StoneThrow C 不保证指针实际上是整数。 C 有一些“奇怪”的实现,其中指针的行为更像是引擎盖下的结构。但是,是的,差不多就是这样。请记住,每个指针都有一个类型,这会影响例如指针算术。 p 未初始化,因此您不知道它指向什么。但是,这与编译代码无关。 【参考方案1】:

6.7.9 初始化

... 14 字符类型的数组可以由字符串字面量或 UTF-8 字符串初始化 字面量,可选地用大括号括起来。字符串文字的连续字节(包括 如果有空间或数组大小未知,则终止空字符)初始化 数组的元素。

C 2011 Online Draft

( *p ? "amet" : "consectetur" ) 不是 字符串文字,也不是评估 字符串文字。它计算为 char * 类型的表达式,它本身不是有效的数组初始化器,并且直到运行时才会进行计算。

更不用说,p 是未初始化的,所以表达式一开始是未定义的。

【讨论】:

如果数组d 的条件初始化有效,sizeof(d) 会返回什么?这是否取决于 p 的初始化对象?当然,您根本无法初始化 VLA。 @JohnBode - 所以,如果我理解正确,并且 wrt pre-C11: char arrays 只能由 "abc" 'a', 'b', 'c, '\0' 等字符串文字初始化(前者隐含后者如果我没记错的话),并且在 : 的任一侧具有两个字符串文字的三元运算 评估为字符串文字,而是评估为 char* - 这是准确的吗? @StoneThrow:正确。在?: 运算符的上下文中,字符串文字被隐式转换为char * 类型的表达式,并且它们计算为每个字符串的第一个字符的地址。请记住,?: 是在运行时计算的,因此结果不能是数组的初始值设定项。【参考方案2】:

字符串文字有点神奇。通常(在表达式中使用时),它们表示数组。数组可以“衰减”(隐式转换)为指针,这就是为什么例如

const char *p = "foo";

有效。 "foo" 是这里的正常表达式。你也可以写

const char *p;
p = "foo";  // assignment, not initialization

但是,如果使用字符串字面量来初始化数组,它的行为类似于初始化字符列表:

char s[] = "foo";
// equivalent to:
char s[] =  'f', 'o', 'o', '\0' ;

在你的例子中

const char d[] = ( *p ? "amet" : "consectetur" );

初始化器不是唯一的字符串文字;它是一种表达方式。因此,两个字符串文字都衰减为指针,然后您会收到错误,因为您无法从指针初始化数组。 (事实上​​,你根本无法从表达式初始化数组。)

【讨论】:

好的,我想我明白了:你不能用指针初始化一个 char 数组 - 我可以用它。当一个字符串文字 - 并且只有一个字符串文字 - 用于初始化一个数组时,它隐含地是一个字符数组。例如。 char c[] = "abc"; 隐含为 char c[] = 'a', 'b', 'c', '\0' ;。但是,当字符串文字位于三元运算符中 : 的任一侧时,它们不再是隐含的 char 数组,而是衰减到它们各自的指针,并且如前所述,数组不能用指针初始化 -这是一个准确的总结吗? @StoneThrow 基本上是的。它似乎暗示 'a', 'b', 'c', '\0' 是一个字符数组,但它不是(它是特殊的初始化语法)。字符串文字是其他任何地方的字符数组。【参考方案3】:

您只能使用初始化列表来初始化数组,而不能使用表达式。在你的代码中

const char* c = ( *p ? "dolor" : "sit" );  

这里c是一个指针变量,字符串常量只代表地址。这就是为什么我们可以使用三元运算符,我们可以将字符串常量的地址分配给指针 c,我们可以初始化数组。但是

const char d[] = ( *p ? "amet" : "consectetur" );

这里 d 是一个数组,但 d 代表数组的地址,字符串常量也只代表地址。因此,我们不能将地址分配给地址,这意味着将字符串常量分配给数组 d。这就是为什么我们会得到错误。

请访问链接以获得更好的理解:

https://www.geeksforgeeks.org/whats-difference-between-char-s-and-char-s-in-c/

Array initialization with a ternary operator?

【讨论】:

我也看到了那个,但接受的答案并不完全符合我的前提。例如。我理解并同意 1, 2 ;不是有效的 C 表达式,而是“amet”;和"consectetur"; 是。 @StoneThrow 但是“amet”;和“consectetur”;是。 这就是为什么你不能用它们来初始化一个数组。初始化器只能是可赋值表达式或初始化器列表。但是你不能给数组赋值。【参考方案4】:

在您尝试定义 d 之前,您是错误的。在尝试定义 c 时,您会问 *p ?但是 p 没有初始化。我相信这种行为没有定义。

【讨论】:

以上是关于为啥使用三元运算符进行数组初始化是非法的?的主要内容,如果未能解决你的问题,请参考以下文章

为啥不能重载三元运算符?

C# - 为啥我不能在字符串中使用三元运算符? [复制]

三元运算符中为啥不能使用braced-init-list?

为啥带赋值的三元运算符不返回预期的输出?

三元运算符为啥以及何时返回左值?

为啥在三元运算符中使用“0”会返回第一个值?