V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
52coder
V2EX  ›  程序员

被初中生 C 语言考住了,尴了个尬

  •  
  •   52coder · 2022-08-28 11:32:15 +08:00 · 10199 次点击
    这是一个创建于 826 天前的主题,其中的信息可能已经有所发展或是发生改变。

    周末来老婆老家,她亲戚有个小孩读初中,有个兴趣班学的 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.有没有踩过这个坑的朋友?

    第 1 条附言  ·  2022-08-28 16:10:59 +08:00
    几年没写 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;
    }
    83 条回复    2022-08-30 14:23:41 +08:00
    kalluwa
        1
    kalluwa  
       2022-08-28 11:36:12 +08:00   ❤️ 10
    这个不是坑,你只写了一个-1 ,那他就只填了第一个元素,其他默认留 0
    dndx
        2
    dndx  
       2022-08-28 11:36:14 +08:00
    https://en.cppreference.com/w/c/language/array_initialization

    没毛病,这是标准定义的行为。
    xtreme1
        3
    xtreme1  
       2022-08-28 11:36:31 +08:00   ❤️ 1
    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.
    yehoshua
        4
    yehoshua  
       2022-08-28 11:37:59 +08:00 via Android
    不完全初始化,只初始化了第一个 arr[0]。这不算什么坑吧,我记得就是这样设定的。
    ailer
        5
    ailer  
       2022-08-28 11:38:19 +08:00 via Android   ❤️ 2
    只有 0 的时候才会全部赋值 0 吧,而且 main 函数里声明数组是不会赋零值的,这个例子第一个是-1 ,后面是什么都有可能
    Privileges
        6
    Privileges  
       2022-08-28 11:39:40 +08:00
    只初始化了数组的第一个元素吧
    bruce0
        7
    bruce0  
       2022-08-28 11:45:03 +08:00   ❤️ 1
    这个我记得一开始学 C 语言的时候,就接触过这个问题, 不同的编译器实现的方式不一样, 有的编译器默认全是 0,有的是随机值. 因为 C 语言标准中没有规定这个东西
    nulIptr
        8
    nulIptr  
       2022-08-28 11:46:09 +08:00   ❤️ 10
    你这属于 c 语言用的少而且很久没用了,你猜 memset 为啥是常用函数
    tyzandhr
        9
    tyzandhr  
       2022-08-28 11:46:48 +08:00 via Android
    标准的 C style 还要把循环里的 int 拿出来呢
    hello2090
        10
    hello2090  
       2022-08-28 11:46:57 +08:00 via iPhone
    凭啥你只初始化了第一个却要求后面 9 个也是-1 ?
    zooo
        11
    zooo  
       2022-08-28 11:46:57 +08:00
    感觉这种用法,在实际中用的不多,比较有迷惑性

    @ailer

    这种题感觉就是记住标准怎么定义的,就清楚了
    zooo
        12
    zooo  
       2022-08-28 11:47:46 +08:00
    @hello2090 那凭啥 只有 0 时候,全部赋值 0 呢?
    52coder
        13
    52coder  
    OP
       2022-08-28 11:48:10 +08:00
    @dndx 是的,我刚也看到这个了,里面有个例子:
    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 ,如果不看上面的例子可能存在误导,以为这么写就是全部初始化
    cherbim
        14
    cherbim  
       2022-08-28 11:54:30 +08:00 via Android
    根据我远古时期记忆,有的默认 0 ,有的可能就随机了
    52coder
        15
    52coder  
    OP
       2022-08-28 11:55:28 +08:00
    @nulIptr memset 不用猜,我好歹也写了 1-2 年 c ,c++ java 里,比如 vector ,可能 {10,-1}这种能指定,c 指定非 0 值还要 memset(arr,-1,sizeof(int)*10)大概这种。
    52coder
        16
    52coder  
    OP
       2022-08-28 11:56:19 +08:00
    @zooo 正解,我查阅文档前记得{0}全是 0 ,然后扩展一下,输入-1 ,结果只有第一个是-1
    hello2090
        17
    hello2090  
       2022-08-28 12:00:20 +08:00 via iPhone
    @zooo 有道理哦,那你继续这么写,坚持自己,别停
    52coder
        18
    52coder  
    OP
       2022-08-28 12:06:55 +08:00
    @hello2090 不是坚持错误的写法哈,是分析{0}全 0 ,换成别的数字就不是全部,这种存在一定的误导性。
    yhxx
        19
    yhxx  
       2022-08-28 12:15:56 +08:00
    这方面 JS 居然还是不错的,会默认 empty
    ffire
        20
    ffire  
       2022-08-28 12:21:20 +08:00 via iPhone
    @52coder 0 在 c 里太特殊了,false 是 0 ,其他都是 true ,返回 0 通常认为无错,指针和 0 搭上关系有时会出问题,等等,不得不说 0 潜移默化表达了很多所谓约定俗成或历史遗留,从这个角度说,那这里规定只是想表达初始化所有成员为 0 ,但我们没有初始化为其他值的简单方式,就容易接受了。
    kfakeman
        21
    kfakeman  
       2022-08-28 12:24:59 +08:00
    @yhxx js 主要是让少考虑内存方面的问题吧
    ffire
        22
    ffire  
       2022-08-28 12:25:47 +08:00 via iPhone   ❤️ 1
    @52coder 另外换个角度,{ 0 }也是初始化第一个为 0 ,后面没指定都是 0 ,刚好达成了所有为 0 的效果。这和{ 1 }第一个为 1 ,后面都 0 的逻辑其实是统一的。
    hello2090
        23
    hello2090  
       2022-08-28 12:27:20 +08:00 via iPhone
    @52coder 我忘了那些情况下会默认初始化为 0 了,你后面 9 个为 0 第一个想到的肯定是被默认初始化了啊,怎么会想成一个 10 元素数组只要提供 1 个值所有元素都能被赋成这个值呢?我无法理解
    SunBK201
        24
    SunBK201  
       2022-08-28 12:30:26 +08:00 via iPhone
    @hello2090 可能是因为这个:

    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
    yanqiyu
        25
    yanqiyu  
       2022-08-28 12:34:57 +08:00
    @ailer 后面一定是 0, 等同于静态初始化

    毕竟一般初始化成 0 我会写成 int name[N] = {};或者 int name[N]{};
    就是利用这个特性偷工减料
    jdhao
        26
    jdhao  
       2022-08-28 12:36:10 +08:00
    yanqiyu
        27
    yanqiyu  
       2022-08-28 12:36:35 +08:00
    @yanqiyu 才注意到题目是 C, 空 list 是 C++特性。但是印象中 C 准备在 23 引入?
    msg7086
        28
    msg7086  
       2022-08-28 12:42:27 +08:00
    没踩过,一直记得是初始化成 0 的。这里第一个元素是-1 ,剩下的元素没有指定值,所以初始化成 0 。

    其实想想就知道不可能全部是-1 了。
    你说 int arr[10] = {-1, -2};,后面的到底是-1 还是-2 还是-3 ?
    honamx
        29
    honamx  
       2022-08-28 12:46:52 +08:00
    没踩过,很简单很基础的题,只是楼主 C 基础不好。
    retrace
        30
    retrace  
       2022-08-28 12:49:45 +08:00
    楼主你还是改行吧
    cmdOptionKana
        31
    cmdOptionKana  
       2022-08-28 13:02:10 +08:00
    C 语言是比较底层的语言,给程序员很大的自由度,很多行为不能靠猜的。楼主实在是太久没写 C 了吧。
    AllenHua
        32
    AllenHua  
       2022-08-28 13:06:07 +08:00
    啊这,arr 数组,下标 0 主动赋值了 -1 ,后面的 9 个坑默认初始化成 0 啊,我记得 C 语言前几节课程就会讲到这点。
    TGl2aWQgZGUgZGll
        33
    TGl2aWQgZGUgZGll  
       2022-08-28 13:17:05 +08:00   ❤️ 1
    小盆友心想:就这还高手?呸~
    TGl2aWQgZGUgZGll
        34
    TGl2aWQgZGUgZGll  
       2022-08-28 13:17:48 +08:00   ❤️ 1
    @zooo #12 后面的 0 都是默认的,不是因为第一个是 0 ,后面都赋值 0
    zooo
        35
    zooo  
       2022-08-28 13:18:47 +08:00
    好像看过一个 C++的规范,尽量不要依赖默认初始化...(不知道是不是有这条)

    所以还是 memset 下
    zooo
        36
    zooo  
       2022-08-28 13:19:57 +08:00
    @TGl2aWQgZGUgZGll
    int w[3] = {0}
    也就是上面这种写法其实没必要?
    wunonglin
        37
    wunonglin  
       2022-08-28 13:20:37 +08:00
    不是。。。按我 js 、go 的思维,初始化了 10 个长度的数组,只给了一个值,那其余的应该都是默认值才对不是么,我觉得没问题
    apake
        38
    apake  
       2022-08-28 14:32:15 +08:00
    很久没写了吧. 默认为 0, 这是 C 的基础
    zhicheng
        39
    zhicheng  
       2022-08-28 14:49:28 +08:00 via iPhone
    “c 指定非 0 值还要 memset(arr,-1,sizeof(int)*10)大概这种。”
    不会写 C 说不会就好,没必要假装。
    microxiaoxiao
        40
    microxiaoxiao  
       2022-08-28 14:54:00 +08:00
    这种叫部分初始化。部分初始化确保了立即给他分配一段内存。它是按照数组下标顺序初始化,没有明确赋予值得会给出 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 ,我之前有个帖子讨论过。
    vanton
        41
    vanton  
       2022-08-28 14:55:41 +08:00
    嗯?
    这个结果不是显而易见的么。
    microxiaoxiao
        42
    microxiaoxiao  
       2022-08-28 14:57:27 +08:00   ❤️ 1
    对非 char 类型的不要用 memset ,达不到想要的效果,要循环初始化。
    betatabe
        43
    betatabe  
       2022-08-28 15:42:41 +08:00
    不少人 memset 都没搞清楚咋用啊
    muzuiget
        44
    muzuiget  
       2022-08-28 15:49:10 +08:00
    本来就是这样,这不是 C 语言的问题,是编译器的问题。

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

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

    换句话说,刚分配内存里面都是“脏数据”,你用了脏数据,就是要后果自负。
    jedihy
        45
    jedihy  
       2022-08-28 15:52:22 +08:00
    {0} 是 C compiler 的特例。所有主流 compiler 的实现都是全部赋值 0 。
    betatabe
        46
    betatabe  
       2022-08-28 15:54:43 +08:00
    @muzuiget c 语言规定了就是 0 哦,前面都发了文档不看的吗
    52coder
        47
    52coder  
    OP
       2022-08-28 16:04:16 +08:00
    @microxiaoxiao
    @nulIptr
    @betatabe
    @zhicheng
    几年没写 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;
    }
    52coder
        48
    52coder  
    OP
       2022-08-28 16:05:38 +08:00
    @betatabe 感谢指出,查了下 wiki 确实记混了,可以通过 tricky 的方法,评论里有不少朋友也存在这个误解。
    ailer
        49
    ailer  
       2022-08-28 16:06:48 +08:00 via Android
    @betatabe 有的编译器没有严格实现这个标准,脏数据是可能存在的
    ailer
        50
    ailer  
       2022-08-28 16:09:35 +08:00 via Android
    @betatabe 而且那个词是 shall ,不是强制的
    betatabe
        51
    betatabe  
       2022-08-28 16:23:08 +08:00
    @ailer 在法律或规则文档中,shall 就是必须的意思
    longbowape
        52
    longbowape  
       2022-08-28 17:42:13 +08:00
    @betatabe must 才是必须,shall 没有强制性,早期的编译器实现就是没有初始化的,所以都需要调用 memset
    dlsflh
        53
    dlsflh  
       2022-08-28 17:57:07 +08:00 via Android
    @longbowape 在需求文档或者合同或者规章中,shall 就是代表一定要完成的条目。
    documentzhangx66
        54
    documentzhangx66  
       2022-08-28 18:10:06 +08:00   ❤️ 1
    类似的还有 b = ++a ; c = a++;

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

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

    int arr[10] = { -1 ,-1 ,-1 ,-1 ,-1 ,-1 ,-1 ,-1 ,-1 ,-1 };
    gamesover
        55
    gamesover  
       2022-08-28 18:52:06 +08:00
    十几年前读书的时候,这就是 c 语言必考题
    如果没有初始化,c 会自动帮你初始化为 0
    fyxtc
        56
    fyxtc  
       2022-08-28 19:26:04 +08:00
    不是坑,{0}也没有误导性,你这典型属于读死书,只记其然而没有知所以然,再说大一学 c 的时候 0 作为数组默认初始化值应该是非常深刻才对
    icyalala
        57
    icyalala  
       2022-08-28 19:47:13 +08:00
    @longbowape http://port70.net/~nsz/c/c99/n1256.html#4p1
    ''shall'' is to be interpreted as a requirement on an implementation or on a program
    phiysng
        58
    phiysng  
       2022-08-28 19:50:05 +08:00
    这不算坑,预期行为很明确的,楼主这是太长时间不用 C/C++了。
    phiysng
        59
    phiysng  
       2022-08-28 19:56:40 +08:00
    ```c++
    void Foo() {
    vector<int> vec(10,-1); // 初始化数组,值为 10 个-1
    for (auto& v : vec) {
    cout << v << endl;
    }
    }
    ```
    C++的 vector 容器有楼主想要的这个行为。
    littlewing
        60
    littlewing  
       2022-08-28 20:31:57 +08:00
    讨论这种东西没意义
    cigarzh
        61
    cigarzh  
       2022-08-28 21:07:40 +08:00
    没意义+1 ,建议直接进行一个魅的祛
    推荐个 https://cppinsights.io
    :)
    YooUzi
        62
    YooUzi  
       2022-08-28 21:19:52 +08:00 via Android
    C 语言不能 for 循环里初始化数吧,C++倒是可以
    mikewang
        63
    mikewang  
       2022-08-28 21:25:42 +08:00
    #15 @52coder
    #42 @microxiaoxiao

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

    换成另外的数字,比如 memset(arr, 1, sizeof(int)*10) 就要出事故了 hhh
    52coder
        64
    52coder  
    OP
       2022-08-28 21:27:26 +08:00
    @ziyifan824 为什么不能?循环遍历初始化一遍是可以的。
    52coder
        65
    52coder  
    OP
       2022-08-28 21:31:16 +08:00
    @documentzhangx66 你这也是人才,如果数组 100 个,1000 个,你就不能教人写个 for 循环吗😁
    microxiaoxiao
        66
    microxiaoxiao  
       2022-08-28 21:32:49 +08:00
    @mikewang ,奇淫巧计自己用肯定没啥问题的,嘿嘿。项目大了,容易出毛病,有时候别人随手一改。
    52coder
        67
    52coder  
    OP
       2022-08-28 21:33:48 +08:00
    @mikewang 是的,-1 和 0 可以通过这种方式初始化,但这种方式不通用,不通用的方式就不要用了,容易误导不知情的吃瓜群众,比如别人复制了代码,修改了初始值,就会导致问题了。
    mikewang
        68
    mikewang  
       2022-08-28 21:40:27 +08:00
    @microxiaoxiao
    @52coder
    各位说的对,正确做法还是循环赋值。
    只是想说明一下 -1 也能出正确结果的原因。

    这里做个提示吧:非标准行为,大家不要学我哦~
    longbowape
        69
    longbowape  
       2022-08-28 22:42:59 +08:00
    @icyalala http://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.
    longbowape
        70
    longbowape  
       2022-08-28 22:46:36 +08:00
    @dlsflh https://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.

    2. MUST NOT This phrase, or the phrase "SHALL NOT", mean that the
    definition is an absolute prohibition of the specification.

    3. 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.

    4. 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.
    longbowape
        71
    longbowape  
       2022-08-28 22:49:23 +08:00
    @dlsflh shall 确实是强制的,我和 should 混了
    gdgoldlion
        72
    gdgoldlion  
       2022-08-28 23:00:01 +08:00
    真想用这类写法,就封装一下
    不能指望所有人都熟悉标准手册

    兴趣班 c 语言成天教这种东西
    老师不咋地
    能退就退了吧
    Kasumi20
        73
    Kasumi20  
       2022-08-29 01:01:04 +08:00
    不会就学
    FirefoxChrome
        74
    FirefoxChrome  
       2022-08-29 01:23:23 +08:00
    C 语言这么麻烦

    对于新手入门来说,学个 C# Python 不好吗?或者 PHP 建个网站
    litguy
        75
    litguy  
       2022-08-29 08:38:49 +08:00
    你的确该尴尬一下,大学教科书都讲清楚了,竟然工作几年还有疑惑
    zhicheng
        76
    zhicheng  
       2022-08-29 08:50:59 +08:00 via iPhone
    @52coder 不太理解这种不会又要硬凹的心理。bool 没有规定就是一个字节,memset 初始化一个字节的数组也不是 trick 。
    rpish
        77
    rpish  
       2022-08-29 09:43:00 +08:00
    兴趣班不应该教 Python 吗?
    这是准备打奥赛?
    ragnaroks
        78
    ragnaroks  
       2022-08-29 18:09:12 +08:00
    用 C 就别用数组了,老实

    int * array = calloc(count,typeSize)
    zhanlanhuizhang
        79
    zhanlanhuizhang  
       2022-08-30 09:25:39 +08:00
    这个一眼就看出来,后面肯定是 0 。大学学的。
    jiulang
        80
    jiulang  
       2022-08-30 10:57:30 +08:00
    如果是 c#,这妥妥的语法异常哈,类似的有:
    if (number = 0)
    {
    // do something
    }
    jiulang
        81
    jiulang  
       2022-08-30 11:00:06 +08:00
    如果语言上没有明确的标准定义,而是靠编译器看着办,那应该在代码上抛弃语言的这个特征
    autumn426
        82
    autumn426  
       2022-08-30 11:10:35 +08:00 via Android
    不写是随机 写了编译器会自己把后面的补 0
    metalbuild
        83
    metalbuild  
       2022-08-30 14:23:41 +08:00
    记忆中 The C Programming Language (2nd Edition) by Brian W. Kernighan, Dennis M. Ritchie 这本书有详细说明 我特意翻查了一下 section 4.9 initialization 本小白只是个半途自学的 有错请指出

    An array may be initialized by following its declaration with a list of initializers enclosed in braces and separated by commas. For example, to initialize an array days with the number of days in each month:

    int days[] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };

    When the size of the array is omitted, the compiler will compute the length by counting the initializers, of which there are 12 in this case.

    >> If there are fewer initializers for an array than the number specified, the missing elements will be zero for extemal, static, and automatic variables. It is an error to have too many initializers. There is no way to specify repetition of an initializer, nor to initialize an element in the middle of an array without supplying all the preceding values as well. <<

    Character arrays are a special case of initialization; a string may be used instead of the braces and commas notation:
    char pattern [] = "ould";
    is a shorthand for the longer but equivalent
    char pattern[] = { 'o', 'u', '1', 'd', '\0' };
    In this case, the array size is five (four characters plus the terminating ' \0').
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   2593 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 30ms · UTC 06:17 · PVG 14:17 · LAX 22:17 · JFK 01:17
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.