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

问道 C 的基础题

  •  
  •   b00tyhunt3r · 2019-04-14 15:56:09 +08:00 · 5324 次点击
    这是一个创建于 2036 天前的主题,其中的信息可能已经有所发展或是发生改变。

    对于以下代码段,正确的说法是:

    
    char *p;
    while (1)
        {
            p = malloc(1);
            *p = 0;
        }
    

    A. 最终程序会因为没有没有空间了而退出

    B. 最终程序会因为向 0 地址写入而退出

    C. 程序会一直运行下去

    D. 程序不能被编译

    主要是内部原理不太明白

    40 条回复    2019-04-30 01:35:24 +08:00
    webdisk
        1
    webdisk  
       2019-04-14 15:57:54 +08:00
    OOM
    jxf2008
        2
    jxf2008  
       2019-04-14 16:00:17 +08:00
    A
    sfqtsh
        3
    sfqtsh  
       2019-04-14 16:00:25 +08:00 via Android
    D.
    AngelCriss
        4
    AngelCriss  
       2019-04-14 16:04:13 +08:00 via Android
    当然是选 B 咯,malloc 失败返回 NULL,解引用 NULL 就挂了,如果没有*p = 0,那就看 oom killer 杀不杀,不杀就一直跑。
    b00tyhunt3r
        5
    b00tyhunt3r  
    OP
       2019-04-14 16:08:56 +08:00
    @AngelCriss
    只有你对了,不过本 noob 第一句就看不懂了,为什么 malloc 会失败,是 OOM 之后才失败吗?姐引用是什么意思?多谢大佬解惑
    kljsandjb
        6
    kljsandjb  
       2019-04-14 16:18:04 +08:00 via iPhone   ❤️ 2
    @b00tyhunt3r #5 内存 run out 的时候,malloc 返回 null 的情况吧…所以一般会在操作内存的时候最好判断是否空指针,像嵌入式开发这种内存较为稀缺的,这种情况会稍微多见一点,话说这是啥题目啊,这么考感觉非常有中国考试特色啊😂
    AngelCriss
        7
    AngelCriss  
       2019-04-14 16:24:59 +08:00
    模拟一下不就知道了
    ```
    ╭─abby@Chameleon /home/abby ‹ system ›
    ╰─$ cat f.c ↵ 139
    #define NULL 0
    char memory[10];
    int idx = 0;

    char* my_malloc()
    {
    if(idx == sizeof(10))
    {
    return NULL;
    }
    char* ptr = memory + idx;
    idx += 1;
    return ptr;
    }

    int main()
    {
    char* p;
    while(1)
    {
    p = my_malloc();
    #ifdef MO
    *p = 0;
    #endif
    }
    }
    ╭─abby@Chameleon /home/abby ‹ system ›
    ╰─$ cc f.c
    ╭─abby@Chameleon /home/abby ‹ system ›
    ╰─$ ./a.out
    ^C⏎ ╭─abby@Chameleon /home/abby ‹ system ›
    ╰─$ cc f.c -DMO ↵ 130
    ╭─abby@Chameleon /home/abby ‹ system ›
    ╰─$ ./a.out
    fish: “./a.out ” terminated by signal SIGSEGV (Address boundary error)
    ╭─abby@Chameleon /home/abby ‹ system ›
    ╰─$
    ```
    @b00tyhunt3r
    webdisk
        8
    webdisk  
       2019-04-14 16:26:28 +08:00
    @kljsandjb 如果一直 malloc(1) 就会出现返回 NULL 的情况, 如果一直 malloc(1024 * 1024) 就会出现 OOM.
    返回 NULL 的时候应该是遇到了 glibc 对内存分配的限制, 或者是 linux 内核对内存分配的限制.
    b00tyhunt3r
        9
    b00tyhunt3r  
    OP
       2019-04-14 16:28:48 +08:00
    @kljsandjb
    浙大幕客题😂😂😂
    其实我不太明白的是,
    定义一个 char 指针 char *p 之后,分配一个字节内存 p = malloc(1);给 p,此刻在计算机内部 p 的状态是什么样的?
    例如 p 自身的位置是 0x100010,
    那么此时:
    ( 1 ) p 指向哪里?
    ( 2 ) OOM 了之后,malloc 函数返回了 NULL 给 p,此刻得到 NULL 的 p 是个什么状态?又指向哪里?
    ( 3 )如果我仅仅只是定义了一个 char *p,但什么也不做(或者定义之后程序还没赋值给 p 这段时间内),
    此刻的内存里,指针 p 是哪里也不指的状态吗?有这种状态的?

    好像教材上没有深入讲
    ashlord
        10
    ashlord  
       2019-04-14 16:35:00 +08:00
    @b00tyhunt3r
    p 指向属于堆( heap )的某块内存
    注意 p 只是一个数值变量,其值恰好是内存地址,所以返回 null 后 p 的值就是 null,即语义上没有指向有效的堆内存。但是由于 c 语言中 null 就是 0,所以会出现对 0 地址写入
    仅定义后 p 就是一个未初始化的变量,具体的值是 compiler i mplementation,c 标准应该没有定义。因此常说变量申明后必须初始化才能使用

    怎么说呢,不要把指针想的太神秘,它就是一个有特殊意义和操作的数值变量……
    alphaprogrammer
        11
    alphaprogrammer  
       2019-04-14 16:35:40 +08:00
    malloc 后需要转为目标类型
    kljsandjb
        12
    kljsandjb  
       2019-04-14 16:36:35 +08:00 via iPhone
    @b00tyhunt3r #9 p 分配在栈空间或者寄存器吧,它的值应该是在堆为你开辟的一块空间的起始位置(也就是指向了堆的某个位置)。你这个相当于死循环一直在开空间,你可以了解一下进程地址空间的概念,看看它的结构,程序怎么在进程上下文运行的,栈、堆的概念等都有,推荐一本书:csapp
    PureWhiteWu
        13
    PureWhiteWu  
       2019-04-14 16:41:42 +08:00
    选 D,没有 include 头文件,编译不过。
    kljsandjb
        14
    kljsandjb  
       2019-04-14 16:41:45 +08:00 via iPhone
    @b00tyhunt3r #9 第三个问题,未初始化的临时变量通常值是不确定的,因为栈始终伴随着程序的调用返回一直在变化,举一个比方,就是刚好一个函数返回,栈指针恢复,但是原先栈中的内容残留了,那么你新建一个临时变量有可能值就是残留的,说白了不用特殊修饰不被优化的临时变量,都是栈地址的表征
    fsafdasfsdafsd
        15
    fsafdasfsdafsd  
       2019-04-14 16:51:15 +08:00
    我的理解是
    malloc 一直分配空间,直到空间满 malloc 分配失败产生 nullptr
    wevsty
        16
    wevsty  
       2019-04-14 16:53:32 +08:00
    D
    malloc(1)返回的类型是 void*,void*直接赋值给 char*编译器会提示错误。
    kljsandjb
        17
    kljsandjb  
       2019-04-14 16:58:03 +08:00 via iPhone
    @wevsty #16 我记得不强制转换只对指针的运算有限制吧,因为不知道运算的步长,赋值应该不会导致编译期的错误
    kljsandjb
        18
    kljsandjb  
       2019-04-14 16:58:30 +08:00 via iPhone
    @wevsty #16 顶多应该是 warnings 吧
    wevsty
        19
    wevsty  
       2019-04-14 17:20:50 +08:00
    @kljsandjb
    试了一下,GCC 上 C 方式编译的话是可以过的,CPP 方式的话过不了。
    看来是我想当然了。
    q8515620
        20
    q8515620  
       2019-04-14 17:30:58 +08:00 via Android   ❤️ 2
    q8515620
        21
    q8515620  
       2019-04-14 17:32:09 +08:00 via Android   ❤️ 1
    好不容易码完字,还不让发。。也是够了
    lance6716
        22
    lance6716  
       2019-04-14 19:26:05 +08:00   ❤️ 1
    求求你看看 CSAPP
    geelaw
        23
    geelaw  
       2019-04-14 20:10:58 +08:00   ❤️ 3
    不能编译通过,因为 while block 必须是函数 block 的后代。

    当然如果假设代码是这样的

    #include<stdlib.h>
    int main(void)
    {
    char *p;
    while (1)
    {
    p = (char *)malloc(1);
    *p = 0;
    }
    }

    那么结论是这四个选项都不对。当 malloc 失败的时候,p = NULL,此后 *p = 0 是未定义行为,程序可以崩溃也可以继续运行,还可能会发射核弹。
    eret9616
        24
    eret9616  
       2019-04-14 21:06:33 +08:00
    所以到最后 也没人给出结论到底应该选什么 真娱乐啊 药丸。。。
    eret9616
        25
    eret9616  
       2019-04-14 21:16:57 +08:00
    @eret9616 所以我来给个结论把 正确答案是 B
    Nerv
        26
    Nerv  
       2019-04-14 21:52:02 +08:00
    win10 gcc 下运行,成功死机
    abccba
        27
    abccba  
       2019-04-14 22:11:30 +08:00 via iPhone
    这个题(已知的信息)没有正确答案吧。不知道 malloc()失败和 oom 谁先来。
    HHehr0ow
        28
    HHehr0ow  
       2019-04-14 22:30:00 +08:00
    jedihy
        29
    jedihy  
       2019-04-15 00:22:28 +08:00 via iPhone
    出这种题的人水平极低
    stephen9357
        30
    stephen9357  
       2019-04-15 00:35:03 +08:00
    B 啊,这也太基础了。不停 malloc,最终会 OOM,但 OOM 并不影响程序的正常运行,指示 malloc 分配失败会返回空指针,这时*p=0 自然就崩掉了。
    usingnamespace
        31
    usingnamespace  
       2019-04-15 00:56:15 +08:00 via iPhone
    @b00tyhunt3r 解引用是 C 最基础的概念,即对指针变量里存的地址进行读取或写入内容 。事实上每个地址你能不能写 ,操作系统都是有数的,比如 NULL 就是一个被定义为 0 的宏,这个就是专门用来表示不可以被解引用的地址 0x00000。说个扩展的,就 malloc 来说,大部分 malloc 会在其返回的指针的前面一小块还存了这段开辟出来的内存的长度
    usingnamespace
        32
    usingnamespace  
       2019-04-15 01:00:15 +08:00 via iPhone
    @b00tyhunt3r 怎么是 c 语言没学好吗?看得都不太想给你解释了。。。如果是大一学生的话希望你一定要把 c 学的扎扎实实的 虽然真正的 c 要在 linux 系统编程才能体现的淋漓精致。。
    azh7138m
        33
    azh7138m  
       2019-04-15 01:22:46 +08:00
    @geelaw 我翻了一下 ISO/IEC DIS 14882:2017(E)里面提到 dereferenceable 的部分似乎也没说是 0 的地方要怎么处理?
    azh7138m
        34
    azh7138m  
       2019-04-15 01:30:26 +08:00   ❤️ 1
    @geelaw 以为是 C++了,C17 里面翻到了
    6.5.3.2 Address and indirection operators
    If an invalid value has been assigned to the pointer, the behavior of the unary * operator is undefined.

    Among the invalid values for dereferencing a pointer by the unary* operator are a null pointer, an address inappropriately aligned for the type of object pointed to, and the address of an object after the end of its lifetime.
    raysonx
        35
    raysonx  
       2019-04-15 09:08:38 +08:00 via Android
    很不幸的是,C 语言中 dereferencing null pointer 是未定义行为,不一定导致程序错误退出。而且如同前面其他人所说,你不能假定 malloc 返回 null 之前不会被 oom kill
    bp0
        36
    bp0  
       2019-04-15 10:09:48 +08:00
    看起来这道题的考察点是分配内存后没有检查返回值则直接使用,但是却有很多漏洞。

    比如,严格说这段程序无法通过编译。因为没有包含头文件,也没有定义函数,而且循环语句无法写在文件作用域的。

    如果不考虑 D。在 Linux 系统中,如果没有修改 overcommit_memory 参数,则有可能在返回 null 之前被 oomkiller 杀掉了,所以 AB 都有可能。

    C 看似最不可能,但如果有无限大的内存和地址空间,为什么不行呢?虽然现实中不存在,但是题目也没说哦。
    kljsandjb
        37
    kljsandjb  
       2019-04-15 20:13:45 +08:00
    b00tyhunt3r
        38
    b00tyhunt3r  
    OP
       2019-04-29 14:07:59 +08:00 via iPad
    @webdisk “如果一直 malloc(1) 就会出现返回 NULL 的情况, 如果一直 malloc(1024 * 1024) 就会出现 OOM.”

    一直 malloc(1)最后不也会因为 oom 而返回 null 吗?和 malloc (1024*1024)有啥区别?后者 oom 了之后不也一样会返回 null 吗
    webdisk
        39
    webdisk  
       2019-04-29 15:13:26 +08:00
    @b00tyhunt3r
    一直 malloc(1) 返回 NULL 是碰到了 glibc|内核 对分配内存块的 数量 的限制, 这里是块的数量
    一直 malloc(1024 * 1024) 出现 OOM, 是因为超过了物理内存和 swap 的总量, 这里总内存量

    首先碰到哪个限制就出现哪个情况.
    如果内存很小, malloc(1) 还没有达到 glibc|内核 限制, 物理内存就光了, 这个时候应该是 OOM
    如果内存巨大, malloc(1024 * 1024) 还没有达到限制, 但是达到了 glibc 的限制, 这时候就会返回 NULL

    没有看代码, 只是做了一种符合观测结果的合理猜测
    OOM 是内核的机制, 杀的进程不一定是运行这个程序的进程, 可能别的进程躺枪
    尤其是设置了不允许对某个进程 OOM kill 的情况.

    个人觉得了解大概就行, 没必要深究这种细节, 碰到自然就明白了
    b00tyhunt3r
        40
    b00tyhunt3r  
    OP
       2019-04-30 01:35:24 +08:00 via iPad
    @webdisk 感谢!
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   2741 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 34ms · UTC 07:23 · PVG 15:23 · LAX 23:23 · JFK 02:23
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.