代码之家  ›  专栏  ›  技术社区  ›  msc

C语言中数组初始化的困惑

  •  98
  • msc  · 技术社区  · 6 年前

    int a[5] = {1,2};
    

    然后,数组中所有未显式初始化的元素都将隐式初始化为零。

    int a[5]={a[2]=1};
    
    printf("%d %d %d %d %d\n", a[0], a[1],a[2], a[3], a[4]);
    

    1 0 1 0 0
    

    我不明白,为什么 a[0] 1 而不是 0 ? 是不明确的行为吗?

    这个问题是在一次采访中提出的。

    7 回复  |  直到 5 年前
        1
  •  96
  •   melpomene    6 年前

    我不认为 int a[5]={a[2]=1};

    有趣的是,对我来说唯一有意义的是你问的那部分: a[0] 设置为 1 因为赋值运算符返回已赋值的值。其他一切都不清楚。

    如果代码是 int a[5] = { [2] = 1 } a[2] 1 以及其他一切 0 { a[2] = 1 } 我们有一个包含赋值表达式的非指定初始值设定项,我们掉进了一个兔子洞。


    • a 必须是局部变量。

      1. 具有静态存储持续时间的对象的初始值设定项中的所有表达式应为常量表达式或字符串文字。

      a[2] = 1 不是常量表达式,所以 必须有自动存储。

    • 6.2.1标识符的范围

      1. 结构、联合和枚举标记的作用域在 类型说明符中声明标记的标记。每个枚举常量的作用域 任何 另一个标识符的作用域在其声明符完成之后才开始。

      a[5] ,因此变量在自己的初始化中处于作用域中。

    • 在它自己的初始化中是活动的。

      6.2.4物品存放期限

      1. 一种对象,其标识符声明时没有链接,也没有存储类 说明符 static 自动存储时间 .

      2. 对于这样一个没有可变长度数组类型的对象, 从进入与其相关联的块,直到该块的执行结束 无论如何。(输入封闭块或调用函数会挂起,但不会结束, 每次都创建对象。对象的初始值是不确定的。如果 如果为对象指定了初始化,则每次执行声明时都会执行初始化 在执行块时达到;否则,值将变得不确定 到达声明的时间。

    • 后面有一个序列点 a[2]=1 .

      1. A 充分表达 不是另一个表达式或声明符的一部分的表达式。 以下每一项都是完整表达式: 初始值设定项 语句;选择语句的控制表达式( if switch );the 控制表达式 while do for 语句中的(可选)表达式 return 声明。 表达式是一个序列点。

      注意,例如 int foo[] = { 1, 2, 3 } 这个 { 1, 2, 3 }

    • 6.7.8初始化

      1. . 当没有 当前对象的类型:按递增下标顺序排列的数组元素、按声明顺序排列的结构成员和联合的第一个命名成员。[...]

      1. 初始化应按初始值设定项列表顺序进行,每个初始值设定项提供一个 重写同一子对象的任何先前列出的初始值设定项的特定子对象;全部 未显式初始化的子对象的初始化方式应与 具有静态存储持续时间的对象。
      1. 初始化列表表达式中出现任何副作用的顺序为 未指定。

    • 序列点是否相关?基本规则是:

      6.5表达式

      1. 最多修改一次 通过表达式的求值 应为只读,以确定要存储的值。

      a[2]=1 是表达式,但初始化不是。

      这与附录J略有矛盾:

      J.2未定义行为

      • 在两个序列点之间,对象被多次修改,或者 读取先前的值而不是确定要存储的值(6.5)。

      附件J说,任何修改都是重要的,而不仅仅是通过表达式进行的修改。但鉴于附件是非规范性的,我们或许可以忽略这一点。

    • 相对于初始值设定项表达式,子对象初始化是如何排序的?是否所有的初始值设定项都是先求值的(以某种顺序),然后用结果初始化子对象(以初始值设定项列表顺序)?或者它们可以交错?


    我想 int a[5] = { a[2] = 1 }

    1. 储存
    2. 执行(唯一)初始值设定项( a[2]=1 在里面 1 .
    3. 那个 用于初始化 a[0] (第一个初始化器初始化第一个子对象)。

    a[1] , a[2] a[3] , a[4] )应该初始化为 0 ,但不清楚什么时候发生的:以前发生过吗 a[2]=1 是否评估?如果是的话, a[2]=1 会“赢”并覆盖 a[2] ,但该赋值是否具有未定义的行为,因为零初始化和赋值表达式之间没有序列点?序列点是否相关(见上文)?或者零初始化是在对所有初始化器求值之后发生的吗?如果是的话, .

    因为C标准没有明确定义这里发生了什么,我认为行为是未定义的(通过省略)。

        2
  •  22
  •   user694733    6 年前

    我不明白,为什么 a[0] 1 而不是 0 ?

    a[2]=1 初始化 a[2] 首先,表达式的结果用于初始化 a[0] .

    6.7.9初始化

    1. 一个接一个

    所以看起来 1 0 0 0 0 也有可能。

    结论:不要编写动态修改初始化变量的初始值设定项。

        3
  •  6
  •   Jonathan Leffler    6 年前

    我认为C11标准涵盖了这种行为,并指出 是 ,我不认为C18在 这个区域。

    本标准的相关章节为 §6.7.9 Initialization . 语法记录如下:

    initializer:
    assignment-expression
    { initializer-list }
    { initializer-list , }
    initializer-list:
    designation opt  initializer
    initializer-list , designation 选择 初始值设定项
    designation:
    designator-list =
    designator-list:
    designator
    designator-list designator
    designator:
    [ constant-expression ]
    . identifier

    注意其中一个术语是 赋值表达式 a[2] = 1 无疑是一个赋值表达式,它被允许在

    §4初始值设定项中的所有表达式 静态或线程存储时间应为常数或 字符串文字。

    §19初始化应按初始值设定项列表顺序进行 为特定子对象提供的初始值设定项重写任何 151) 所有未显式初始化的子对象都应 与具有静态存储的对象隐式初始化相同 持续时间。

    151) 因此不用于初始化该子对象时可能不会计算 全部。

    另一个关键段落是:

    §23初始化列表表达式的计算如下: 152)

    152) 特别是,评估顺序不必是 与子对象初始化的顺序相同。

    我相当确信第23段表明 问题:

    int a[5] = { a[2] = 1 };
    

    分配给 a[2] 是一种副作用,其评价顺序为 表达式之间的顺序是不确定的。 声明特定的编译器正确或错误地处理了这个问题。

        4
  •  2
  •   Karthika    6 年前

    我的理解是 a[2]=1 返回值 1 所以代码变成

    int a[5]={a[2]=1} --> int a[5]={1}
    

    int a[5]={1} 赋值

    因此,它的打印 1 对于

    char str[10]={‘H’,‘a’,‘i’};
    
    
    char str[0] = ‘H’;
    char str[1] = ‘a’;
    char str[2] = ‘i;
    
        5
  •  1
  •   Battle    6 年前

    我试着给这个难题一个简短的答案: int a[5] = { a[2] = 1 };

    1. a[2] = 1 0 0 1 0 0
    2. 看哪,既然你是在耶和华面前行的 { } 1 )设置为 a[0] . 好像 int a[5] = { a[2] }; a[2]=1 . 现在生成的数组是: 1 0 1 0 0

    另一个例子: int a[6] = { a[3] = 1, a[4] = 2, a[5] = 3 };

    0 0 0 1 0 0
    1 0 0 1 0 0
    1 0 0 1 2 0
    1 2 0 1 2 0
    1 2 0 1 2 3
    1 2 3 1 2 3
    
        6
  •  0
  •   Yves Daoust    6 年前

    a[2]= 1 是具有值的表达式 1 ,你基本上是这样写的 int a[5]= { 1 }; (副作用是 a[2] 已分配 1

        7
  •  0
  •   Sven    6 年前

    我相信,那 int a[5]={ a[2]=1 }; 对于程序员来说,这是一个很好的例子。

    int a[5]={ [2]=1 }; 它将是一个C99指定的初始值设定项,将元素2设置为1,将其余元素设置为0。

    在这种罕见的情况下 int a[5]={ 1 }; a[2]=1; ,那么这将是一个有趣的写作方式。不管怎么说,这就是你的代码归结起来,即使这里有人指出它没有很好的定义时,写入 a[2] a[2]=1