周末来老婆老家,她亲戚有个小孩读初中,有个兴趣班学的 C 语言,得知我是从事软件开发 5 6 年的“高手”,饭后问了我一道编程题,我三俩下就告诉他怎么 怎么写,结果提交的时候始终显示有问题,一排查发现这里有坑,我手写一个 demo (可能编译不过哈)请教各位如下程序输出是什么?
#include <stdio.h>
int main()
{
int arr[10] = {-1};
//打印 arr 全部内容
for(int i = 0;i < 10;i++)
printf("%d",arr[i]);

return 0;
}

我之前一直以为会全部输出-1 ,结果在 gcc 11.2.0 的环境下,输出确实一个-1 ,然后全是 0.有没有踩过这个坑的朋友?

几年没写 C 代码有点脱离一线了,使用 memset 是对每个 byte 操作,针对 int 这里不适用,印象中我司代码中有一些 tricky 的方法,我网上找了个例子,可以变相达到这个目的,评论里有朋友说 memset ,还有我评论的例子 memset(arr,10,sizeof(int)*100)这种是错的,每个字节都设置成 0x10,那么数组每个元素都是 0x10101010
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h> //Use C99 standard for C language which supports bool variables

int main()
{
int i, cnt = 5;
bool *hash = NULL;
hash = malloc(cnt);

memset(hash, 1, cnt);
printf("Hello, World!\n");

for(i=0; i<cnt; i++)
printf("%d ", hash[i]);

return 0;
}

这个不是坑,你只写了一个-1 ,那他就只填了第一个元素,其他默认留 0

en.cppreference.com/w/c/language/array_initialization

没毛病,这是标准定义的行为。

C99 Standard 6.7.8.21

If there are fewer initializers in a brace-enclosed list than there are elements or members of an aggregate, or fewer characters in a string literal used to initialize an array of known size than there are elements in the array, the remainder of the aggregate shall be initialized implicitly the same as objects that have static storage duration.

不完全初始化,只初始化了第一个 arr[0]。这不算什么坑吧,我记得就是这样设定的。

只有 0 的时候才会全部赋值 0 吧,而且 main 函数里声明数组是不会赋零值的,这个例子第一个是-1 ,后面是什么都有可能

只初始化了数组的第一个元素吧

这个我记得一开始学 C 语言的时候,就接触过这个问题, 不同的编译器实现的方式不一样, 有的编译器默认全是 0,有的是随机值. 因为 C 语言标准中没有规定这个东西

你这属于 c 语言用的少而且很久没用了,你猜 memset 为啥是常用函数

标准的 C style 还要把循环里的 int 拿出来呢

凭啥你只初始化了第一个却要求后面 9 个也是-1 ?

感觉这种用法,在实际中用的不多,比较有迷惑性

这种题感觉就是记住标准怎么定义的,就清楚了

那凭啥 只有 0 时候,全部赋值 0 呢?

是的,我刚也看到这个了,里面有个例子:
int z[4] = {1}; // z has type int[4] and holds 1,0,0,0
int w[3] = {0}; // w has type int[3] and holds all zeroes
以前写过 1-2 年 C ,这么写{0} 全部都是 0 ,如果不看上面的例子可能存在误导,以为这么写就是全部初始化

根据我远古时期记忆,有的默认 0 ,有的可能就随机了

memset 不用猜,我好歹也写了 1-2 年 c ,c++ java 里,比如 vector ,可能 {10,-1}这种能指定,c 指定非 0 值还要 memset(arr,-1,sizeof(int)*10)大概这种。

正解,我查阅文档前记得{0}全是 0 ,然后扩展一下,输入-1 ,结果只有第一个是-1

有道理哦,那你继续这么写,坚持自己,别停

不是坚持错误的写法哈,是分析{0}全 0 ,换成别的数字就不是全部,这种存在一定的误导性。

这方面 JS 居然还是不错的,会默认 empty

0 在 c 里太特殊了,false 是 0 ,其他都是 true ,返回 0 通常认为无错,指针和 0 搭上关系有时会出问题,等等,不得不说 0 潜移默化表达了很多所谓约定俗成或历史遗留,从这个角度说,那这里规定只是想表达初始化所有成员为 0 ,但我们没有初始化为其他值的简单方式,就容易接受了。

js 主要是让少考虑内存方面的问题吧

另外换个角度,{ 0 }也是初始化第一个为 0 ,后面没指定都是 0 ,刚好达成了所有为 0 的效果。这和{ 1 }第一个为 1 ,后面都 0 的逻辑其实是统一的。

我忘了那些情况下会默认初始化为 0 了,你后面 9 个为 0 第一个想到的肯定是被默认初始化了啊,怎么会想成一个 10 元素数组只要提供 1 个值所有元素都能被赋成这个值呢?我无法理解

可能是因为这个:

int s[5]={0};
00EA17BE mov dword ptr [ebp-18h],0
00EA17C5 xor eax,eax
00EA17C7 mov dword ptr [ebp-14h],eax
00EA17CA mov dword ptr [ebp-10h],eax
00EA17CD mov dword ptr [ebp-0Ch],eax
00EA17D0 mov dword ptr [ebp-8],eax

后面一定是 0, 等同于静态初始化

毕竟一般初始化成 0 我会写成 int name[N] = {};或者 int name[N]{};
就是利用这个特性偷工减料

stackoverflow.com/a/17528324/6064933

才注意到题目是 C, 空 list 是 C++特性。但是印象中 C 准备在 23 引入?

没踩过,一直记得是初始化成 0 的。这里第一个元素是-1 ,剩下的元素没有指定值,所以初始化成 0 。

其实想想就知道不可能全部是-1 了。
你说 int arr[10] = {-1, -2};,后面的到底是-1 还是-2 还是-3 ?

没踩过,很简单很基础的题,只是楼主 C 基础不好。

楼主你还是改行吧

C 语言是比较底层的语言,给程序员很大的自由度,很多行为不能靠猜的。楼主实在是太久没写 C 了吧。

啊这,arr 数组,下标 0 主动赋值了 -1 ,后面的 9 个坑默认初始化成 0 啊,我记得 C 语言前几节课程就会讲到这点。

小盆友心想:就这还高手?呸~

#12 后面的 0 都是默认的,不是因为第一个是 0 ,后面都赋值 0

好像看过一个 C++的规范,尽量不要依赖默认初始化...(不知道是不是有这条)

所以还是 memset 下

int w[3] = {0}
也就是上面这种写法其实没必要?

不是。。。按我 js 、go 的思维,初始化了 10 个长度的数组,只给了一个值,那其余的应该都是默认值才对不是么,我觉得没问题

很久没写了吧. 默认为 0, 这是 C 的基础

“c 指定非 0 值还要 memset(arr,-1,sizeof(int)*10)大概这种。”
不会写 C 说不会就好,没必要假装。

这种叫部分初始化。部分初始化确保了立即给他分配一段内存。它是按照数组下标顺序初始化,没有明确赋予值得会给出 0 值,如果理解了这一条其实就能明白它仅仅初始化了数组 0 。再举个显眼的例子,int num[3] = { 5, 7 };这种你就能明白了。按照顺序初始化 0 ,1 下标得值,得到 num[0] = 5, num[1] = 7 ,其余得赋值为 0 , 如果想跳过 1 初始化也是 OK 的, int num[3] = { [0]=5,[2]= 7 }。 这样应该就不会困惑了。至于为啥要一般写法 memset ,我之前有个帖子讨论过。

嗯?
这个结果不是显而易见的么。

对非 char 类型的不要用 memset ,达不到想要的效果,要循环初始化。

不少人 memset 都没搞清楚咋用啊

本来就是这样,这不是 C 语言的问题,是编译器的问题。

编译器看到“int arr[10]”,生成“分配 10 * 4 个字节长内存”的指令,然后再生成“把首个字节的设置为 -1 的指令”。

所以关键是分配内存这个指令问题,分配的内存可能是曾经使用过的内存地址,里面的数据可能是曾经使用过的,可以是任意数据;也很大机率是全新的未使用地址,数据全是 0 。

换句话说,刚分配内存里面都是“脏数据”,你用了脏数据,就是要后果自负。

{0} 是 C compiler 的特例。所有主流 compiler 的实现都是全部赋值 0 。

c 语言规定了就是 0 哦,前面都发了文档不看的吗

几年没写 C 代码有点脱离一线了,使用 memset 是对每个 byte 操作,针对 int 这里不适用,印象中我司代码中有一些 tricky 的方法,我网上找了个例子,可以变相达到这个目的:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h> //Use C99 standard for C language which supports bool variables

int main()
{
int i, cnt = 5;
bool *hash = NULL;
hash = malloc(cnt);

memset(hash, 1, cnt);
printf("Hello, World!\n");

for(i=0; i<cnt; i++)
printf("%d ", hash[i]);

return 0;
}

感谢指出,查了下 wiki 确实记混了,可以通过 tricky 的方法,评论里有不少朋友也存在这个误解。

有的编译器没有严格实现这个标准,脏数据是可能存在的

而且那个词是 shall ,不是强制的

在法律或规则文档中,shall 就是必须的意思

must 才是必须,shall 没有强制性,早期的编译器实现就是没有初始化的,所以都需要调用 memset

在需求文档或者合同或者规章中,shall 就是代表一定要完成的条目。

类似的还有 b = ++a ; c = a++;

我以前遇到有人问这种问题,我的建议是自动离职,别害团队了。这类东西除了在上学期间,应付奇葩的老师之外,没有半点益处,甚至还是团队毒瘤。

楼主,他问你这种问题,你应该教他正确的写法是:

int arr[10] = { -1 ,-1 ,-1 ,-1 ,-1 ,-1 ,-1 ,-1 ,-1 ,-1 };

十几年前读书的时候,这就是 c 语言必考题
如果没有初始化,c 会自动帮你初始化为 0

不是坑,{0}也没有误导性,你这典型属于读死书,只记其然而没有知所以然,再说大一学 c 的时候 0 作为数组默认初始化值应该是非常深刻才对

port70.net/~nsz/c/c99/n1256.html#4p1
''shall'' is to be interpreted as a requirement on an implementation or on a program

这不算坑,预期行为很明确的,楼主这是太长时间不用 C/C++了。

void Foo() {
 vector<int> vec(10,-1); // 初始化数组,值为 10 个-1
 for (auto& v : vec) {
 cout << v << endl;
 }
}

C++的 vector 容器有楼主想要的这个行为。

讨论这种东西没意义

没意义+1 ,建议直接进行一个魅的祛
推荐个 cppinsights.io
:)

C 语言不能 for 循环里初始化数吧,C++倒是可以

#15
#42
补充一下,恰巧在全填 -1 的情况下,memset 可以正常工作。memset(arr,-1,sizeof(int)*10) 没问题
-1 的补码为全 1 ,因此不管是多少 bit 的有符号类型,值都是 -1 。
另外一个特例,就是 0 (全 0 )。

换成另外的数字,比如 memset(arr, 1, sizeof(int)*10) 就要出事故了 hhh

为什么不能?循环遍历初始化一遍是可以的。

你这也是人才,如果数组 100 个,1000 个,你就不能教人写个 for 循环吗😁

,奇淫巧计自己用肯定没啥问题的,嘿嘿。项目大了,容易出毛病,有时候别人随手一改。

是的,-1 和 0 可以通过这种方式初始化,但这种方式不通用,不通用的方式就不要用了,容易误导不知情的吃瓜群众,比如别人复制了代码,修改了初始值,就会导致问题了。

各位说的对,正确做法还是循环赋值。
只是想说明一下 -1 也能出正确结果的原因。

这里做个提示吧:非标准行为,大家不要学我哦~

port70.net/~nsz/c/c99/n1256.html#4p2 If a ''shall'' or ''shall not'' requirement that appears outside of a constraint is violated, the behavior is undefined.

www.rfc-editor.org/rfc/rfc2119.html

  1. MUST This word, or the terms "REQUIRED" or "SHALL", mean that the
    definition is an absolute requirement of the specification.
  1. MUST NOT This phrase, or the phrase "SHALL NOT", mean that the
    definition is an absolute prohibition of the specification.
  2. SHOULD This word, or the adjective "RECOMMENDED", mean that there
    may exist valid reasons in particular circumstances to ignore a
    particular item, but the full implications must be understood and
    carefully weighed before choosing a different course.
  3. SHOULD NOT This phrase, or the phrase "NOT RECOMMENDED" mean that
    there may exist valid reasons in particular circumstances when the
    particular behavior is acceptable or even useful, but the full
    implications should be understood and the case carefully weighed
    before implementing any behavior described with this label.

    shall 确实是强制的,我和 should 混了

真想用这类写法,就封装一下
不能指望所有人都熟悉标准手册

兴趣班 c 语言成天教这种东西
老师不咋地
能退就退了吧

不会就学

C 语言这么麻烦

对于新手入门来说,学个 C# Python 不好吗?或者 PHP 建个网站

你的确该尴尬一下,大学教科书都讲清楚了,竟然工作几年还有疑惑

不太理解这种不会又要硬凹的心理。bool 没有规定就是一个字节,memset 初始化一个字节的数组也不是 trick 。

兴趣班不应该教 Python 吗?
这是准备打奥赛?

用 C 就别用数组了,老实

int * array = calloc(count,typeSize)