热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

关于C中数组初始化的困惑

如何解决《关于C中数组初始化的困惑》经验,为你挑选了3个好方法。

在C语言中,如果初始化一个这样的数组:

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?是不确定的行为?

注意:这个问题是在接受采访时提出的.



1> melpomene..:

TL; DR:int a[5]={a[2]=1};至少在C99中,我认为行为并不明确.

有趣的是,对我来说唯一有意义的是您要问的部分:a[0]设置为1因为赋值运算符返回已分配的值.这是其他一切不清楚的事情.

如果代码已经存在int a[5] = { [2] = 1 },那么一切都会很简单:这是指定的初始化设置a[2],1以及其他所有内容0.但是,由于{ a[2] = 1 }我们有一个包含赋值表达式的非指定初始值设定项,因此我们陷入了一个兔子洞.


这是我到目前为止所发现的:

a 必须是局部变量.

6.7.8初始化

    具有静态存储持续时间的对象的初始化程序中的所有表达式应为常量表达式或字符串文字.

a[2] = 1不是一个常量表达式,因此a必须具有自动存储功能.

a 在自己的初始化范围内.

6.2.1标识符的范围

    结构,联合和枚举标记具有在声明标记的类型说明符中标记出现之后开始的范围.每个枚举常量都具有在枚举器列表中定义枚举器出现之后开始的范围.任何其他标识符的范围都在其声明者完成之后开始.

声明符是a[5],因此变量在它们自己的初始化范围内.

a 在自己的初始化中还活着.

6.2.4对象的存储持续时间

    声明标识符没有链接且没有存储类说明符的对象static具有自动存储持续时间.

    对于没有可变长度数组类型的此类对象,其生命周期从entry进入与其关联的块,直到该块的执行以任何方式结束.(输入一个封闭的块或调用一个函数暂停,但不会结束,执行当前块.)如果以递归方式输入块,则每次都会创建一个新的对象实例.对象的初始值是不确定的.如果为对象指定了初始化,则每次在执行块时达到声明时都会执行初始化; 否则,每次达到声明时,该值将变为不确定.

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

6.8声明和块

    充分表达是不是另一个表达式或一声明符的一部分的表达.以下每个都是完整表达式:初始化器 ; 表达式中的表达式; 选择陈述(ifswitch)的控制表达; 一个while或一个do陈述的控制表达; for语句中的每个(可选)表达式; return语句中的(可选)表达式.完整表达式的结尾是序列点.

请注意,例如,在int foo[] = { 1, 2, 3 }{ 1, 2, 3 }部分中是一个括号括起来的初始化器列表,每个初始化器都有一个序列点.

初始化在初始化列表顺序中执行.

6.7.8初始化

    每个大括号括起的初始化列表都有一个关联的当前对象.当没有指定时,根据当前对象的类型按顺序初始化当前对象的子对象:增加下标顺序的数组元素,声明顺序中的结构成员,以及union的第一个命名成员.[...]

 

    初始化应在初始化器列表顺序中进行,每个初始化器为特定子对象提供,覆盖同一子对象的任何先前列出的初始化器; 未明确初始化的所有子对象应与具有静态存储持续时间的对象隐式初始化.

但是,初始化表达式不一定按顺序进行评估.

6.7.8初始化

    未指定初始化列表表达式中出现任何副作用的顺序.


但是,这仍然有一些问题没有答案:

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

6.5表达式

    在前一个和下一个序列点之间,对象的存储值最多只能通过表达式的计算来修改一次.此外,先前的值应该只读以确定要存储的值.

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

这与附件J略有矛盾:

J.2未定义的行为

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

附件J表示任何修改都很重要,而不仅仅是表达式的修改.但鉴于附件是非规范性的,我们可能会忽略这一点.

如何针对初始化表达式对子对象初始化进行排序?是否首先评估所有初始值设定项(按某种顺序),然​​后使用结果初始化子对象(在初始化列表顺序中)?或者它们可以交错吗?


我认为int a[5] = { a[2] = 1 }执行如下:

    存储for a在输入其包含块时分配.此时内容是不确定的.

    执行(仅)初始化程序(a[2] = 1),然后执行序列点.该文件存储1a[2]和回报1.

    1用于初始化a[0](第一个初始化器初始化第一个子对象).

但在这里事情变得模糊,因为剩余的元素(a[1],a[2],a[3],a[4]),都应该被初始化为0,但目前还不清楚时:它发生之前a[2] = 1进行评估?如果是这样,a[2] = 1将"赢"并覆盖a[2],但是该赋值是否具有未定义的行为,因为零初始化和赋值表达式之间没有序列点?序列点是否相关(见上文)?或者在评估所有初始化程序后是否进行零初始化?如果是这样,a[2]应该最终成为0.

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


@Someprogrammerdude我不认为它可以不被指定("*国际标准提供两种或更多种可能性的行为,并且在任何情况下都没有选择任何进一步的要求*")因为该标准实际上没有提供任何可能性哪个可供选择.它只是没有说明发生了什么,我认为这属于"*未定义的行为在本国际标准[...]中通过省略任何明确的行为定义来表明.*"
@BЈовић这也是一个非常好的描述,不仅对于未定义的行为,而且对于需要像这个解释的线程的定义行为.

2> user694733..:

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

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

从N2176(C17草案):

6.7.9初始化

    初始化列表表达式的评估相对于彼此不确定地排序,因此未指定任何副作用发生的顺序. 154)

所以似乎输出1 0 0 0 0也是可能的.

结论:不要编写初始化程序来动态修改初始化变量.



3> Jonathan Lef..:

我认为C11标准涵盖了这种行为,并说结果没有说明,我不认为C18在这方面做了任何相关的改变.

标准语言不容易解析.标准的相关部分是 §6.7.9初始化.语法记录为:

initializer:
                assignment-expression
                { initializer-list }
                { initializer-list , }
initializer-list:
                designationopt initializer
                initializer-list , designationopt initializer
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]是副作用,并且表达式的评估顺序相对于彼此不确定地排序.因此,我认为没有办法诉诸标准并声称特定编译器正确或错误地处理此问题.


推荐阅读
  • 本文讨论了一个关于cuowu类的问题,作者在使用cuowu类时遇到了错误提示和使用AdjustmentListener的问题。文章提供了16个解决方案,并给出了两个可能导致错误的原因。 ... [详细]
  • Iamtryingtomakeaclassthatwillreadatextfileofnamesintoanarray,thenreturnthatarra ... [详细]
  • 向QTextEdit拖放文件的方法及实现步骤
    本文介绍了在使用QTextEdit时如何实现拖放文件的功能,包括相关的方法和实现步骤。通过重写dragEnterEvent和dropEvent函数,并结合QMimeData和QUrl等类,可以轻松实现向QTextEdit拖放文件的功能。详细的代码实现和说明可以参考本文提供的示例代码。 ... [详细]
  • android listview OnItemClickListener失效原因
    最近在做listview时发现OnItemClickListener失效的问题,经过查找发现是因为button的原因。不仅listitem中存在button会影响OnItemClickListener事件的失效,还会导致单击后listview每个item的背景改变,使得item中的所有有关焦点的事件都失效。本文给出了一个范例来说明这种情况,并提供了解决方法。 ... [详细]
  • 前景:当UI一个查询条件为多项选择,或录入多个条件的时候,比如查询所有名称里面包含以下动态条件,需要模糊查询里面每一项时比如是这样一个数组条件:newstring[]{兴业银行, ... [详细]
  • 本文介绍了iOS数据库Sqlite的SQL语句分类和常见约束关键字。SQL语句分为DDL、DML和DQL三种类型,其中DDL语句用于定义、删除和修改数据表,关键字包括create、drop和alter。常见约束关键字包括if not exists、if exists、primary key、autoincrement、not null和default。此外,还介绍了常见的数据库数据类型,包括integer、text和real。 ... [详细]
  • 本文介绍了深入浅出Linux设备驱动编程的重要性,以及两种加载和删除Linux内核模块的方法。通过一个内核模块的例子,展示了模块的编译和加载过程,并讨论了模块对内核大小的控制。深入理解Linux设备驱动编程对于开发者来说非常重要。 ... [详细]
  • 本文介绍了如何使用Express App提供静态文件,同时提到了一些不需要使用的文件,如package.json和/.ssh/known_hosts,并解释了为什么app.get('*')无法捕获所有请求以及为什么app.use(express.static(__dirname))可能会提供不需要的文件。 ... [详细]
  • Java太阳系小游戏分析和源码详解
    本文介绍了一个基于Java的太阳系小游戏的分析和源码详解。通过对面向对象的知识的学习和实践,作者实现了太阳系各行星绕太阳转的效果。文章详细介绍了游戏的设计思路和源码结构,包括工具类、常量、图片加载、面板等。通过这个小游戏的制作,读者可以巩固和应用所学的知识,如类的继承、方法的重载与重写、多态和封装等。 ... [详细]
  • Java序列化对象传给PHP的方法及原理解析
    本文介绍了Java序列化对象传给PHP的方法及原理,包括Java对象传递的方式、序列化的方式、PHP中的序列化用法介绍、Java是否能反序列化PHP的数据、Java序列化的原理以及解决Java序列化中的问题。同时还解释了序列化的概念和作用,以及代码执行序列化所需要的权限。最后指出,序列化会将对象实例的所有字段都进行序列化,使得数据能够被表示为实例的序列化数据,但只有能够解释该格式的代码才能够确定数据的内容。 ... [详细]
  • 本文探讨了C语言中指针的应用与价值,指针在C语言中具有灵活性和可变性,通过指针可以操作系统内存和控制外部I/O端口。文章介绍了指针变量和指针的指向变量的含义和用法,以及判断变量数据类型和指向变量或成员变量的类型的方法。还讨论了指针访问数组元素和下标法数组元素的等价关系,以及指针作为函数参数可以改变主调函数变量的值的特点。此外,文章还提到了指针在动态存储分配、链表创建和相关操作中的应用,以及类成员指针与外部变量的区分方法。通过本文的阐述,读者可以更好地理解和应用C语言中的指针。 ... [详细]
  • Android源码深入理解JNI技术的概述和应用
    本文介绍了Android源码中的JNI技术,包括概述和应用。JNI是Java Native Interface的缩写,是一种技术,可以实现Java程序调用Native语言写的函数,以及Native程序调用Java层的函数。在Android平台上,JNI充当了连接Java世界和Native世界的桥梁。本文通过分析Android源码中的相关文件和位置,深入探讨了JNI技术在Android开发中的重要性和应用场景。 ... [详细]
  • ALTERTABLE通过更改、添加、除去列和约束,或者通过启用或禁用约束和触发器来更改表的定义。语法ALTERTABLEtable{[ALTERCOLUMNcolu ... [详细]
  • Java学习笔记之面向对象编程(OOP)
    本文介绍了Java学习笔记中的面向对象编程(OOP)内容,包括OOP的三大特性(封装、继承、多态)和五大原则(单一职责原则、开放封闭原则、里式替换原则、依赖倒置原则)。通过学习OOP,可以提高代码复用性、拓展性和安全性。 ... [详细]
  • 本文讨论了clone的fork与pthread_create创建线程的不同之处。进程是一个指令执行流及其执行环境,其执行环境是一个系统资源的集合。在调用系统调用fork创建一个进程时,子进程只是完全复制父进程的资源,这样得到的子进程独立于父进程,具有良好的并发性。但是二者之间的通讯需要通过专门的通讯机制,另外通过fork创建子进程系统开销很大。因此,在某些情况下,使用clone或pthread_create创建线程可能更加高效。 ... [详细]
author-avatar
mobiledu2502899157
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有