对于以下代码段,正确的说法是:
char *p;
while (1)
{
p = malloc(1);
*p = 0;
}
A. 最终程序会因为没有没有空间了而退出
B. 最终程序会因为向 0 地址写入而退出
C. 程序会一直运行下去
D. 程序不能被编译
主要是内部原理不太明白
1
webdisk 2019-04-14 15:57:54 +08:00
OOM
|
2
jxf2008 2019-04-14 16:00:17 +08:00
A
|
3
sfqtsh 2019-04-14 16:00:25 +08:00 via Android
D.
|
4
AngelCriss 2019-04-14 16:04:13 +08:00 via Android
当然是选 B 咯,malloc 失败返回 NULL,解引用 NULL 就挂了,如果没有*p = 0,那就看 oom killer 杀不杀,不杀就一直跑。
|
5
b00tyhunt3r OP @AngelCriss
只有你对了,不过本 noob 第一句就看不懂了,为什么 malloc 会失败,是 OOM 之后才失败吗?姐引用是什么意思?多谢大佬解惑 |
6
kljsandjb 2019-04-14 16:18:04 +08:00 via iPhone 2
@b00tyhunt3r #5 内存 run out 的时候,malloc 返回 null 的情况吧…所以一般会在操作内存的时候最好判断是否空指针,像嵌入式开发这种内存较为稀缺的,这种情况会稍微多见一点,话说这是啥题目啊,这么考感觉非常有中国考试特色啊😂
|
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 |
8
webdisk 2019-04-14 16:26:28 +08:00
@kljsandjb 如果一直 malloc(1) 就会出现返回 NULL 的情况, 如果一直 malloc(1024 * 1024) 就会出现 OOM.
返回 NULL 的时候应该是遇到了 glibc 对内存分配的限制, 或者是 linux 内核对内存分配的限制. |
9
b00tyhunt3r OP @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 是哪里也不指的状态吗?有这种状态的? 好像教材上没有深入讲 |
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 标准应该没有定义。因此常说变量申明后必须初始化才能使用 怎么说呢,不要把指针想的太神秘,它就是一个有特殊意义和操作的数值变量…… |
11
alphaprogrammer 2019-04-14 16:35:40 +08:00
malloc 后需要转为目标类型
|
12
kljsandjb 2019-04-14 16:36:35 +08:00 via iPhone
@b00tyhunt3r #9 p 分配在栈空间或者寄存器吧,它的值应该是在堆为你开辟的一块空间的起始位置(也就是指向了堆的某个位置)。你这个相当于死循环一直在开空间,你可以了解一下进程地址空间的概念,看看它的结构,程序怎么在进程上下文运行的,栈、堆的概念等都有,推荐一本书:csapp
|
13
PureWhiteWu 2019-04-14 16:41:42 +08:00
选 D,没有 include 头文件,编译不过。
|
14
kljsandjb 2019-04-14 16:41:45 +08:00 via iPhone
@b00tyhunt3r #9 第三个问题,未初始化的临时变量通常值是不确定的,因为栈始终伴随着程序的调用返回一直在变化,举一个比方,就是刚好一个函数返回,栈指针恢复,但是原先栈中的内容残留了,那么你新建一个临时变量有可能值就是残留的,说白了不用特殊修饰不被优化的临时变量,都是栈地址的表征
|
15
fsafdasfsdafsd 2019-04-14 16:51:15 +08:00
我的理解是
malloc 一直分配空间,直到空间满 malloc 分配失败产生 nullptr |
16
wevsty 2019-04-14 16:53:32 +08:00
D
malloc(1)返回的类型是 void*,void*直接赋值给 char*编译器会提示错误。 |
17
kljsandjb 2019-04-14 16:58:03 +08:00 via iPhone
@wevsty #16 我记得不强制转换只对指针的运算有限制吧,因为不知道运算的步长,赋值应该不会导致编译期的错误
|
20
q8515620 2019-04-14 17:30:58 +08:00 via Android 2
|
21
q8515620 2019-04-14 17:32:09 +08:00 via Android 1
好不容易码完字,还不让发。。也是够了
|
22
lance6716 2019-04-14 19:26:05 +08:00 1
求求你看看 CSAPP
|
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 是未定义行为,程序可以崩溃也可以继续运行,还可能会发射核弹。 |
24
eret9616 2019-04-14 21:06:33 +08:00
所以到最后 也没人给出结论到底应该选什么 真娱乐啊 药丸。。。
|
26
Nerv 2019-04-14 21:52:02 +08:00
win10 gcc 下运行,成功死机
|
27
abccba 2019-04-14 22:11:30 +08:00 via iPhone
这个题(已知的信息)没有正确答案吧。不知道 malloc()失败和 oom 谁先来。
|
29
jedihy 2019-04-15 00:22:28 +08:00 via iPhone
出这种题的人水平极低
|
30
stephen9357 2019-04-15 00:35:03 +08:00
B 啊,这也太基础了。不停 malloc,最终会 OOM,但 OOM 并不影响程序的正常运行,指示 malloc 分配失败会返回空指针,这时*p=0 自然就崩掉了。
|
31
usingnamespace 2019-04-15 00:56:15 +08:00 via iPhone
@b00tyhunt3r 解引用是 C 最基础的概念,即对指针变量里存的地址进行读取或写入内容 。事实上每个地址你能不能写 ,操作系统都是有数的,比如 NULL 就是一个被定义为 0 的宏,这个就是专门用来表示不可以被解引用的地址 0x00000。说个扩展的,就 malloc 来说,大部分 malloc 会在其返回的指针的前面一小块还存了这段开辟出来的内存的长度
|
32
usingnamespace 2019-04-15 01:00:15 +08:00 via iPhone
@b00tyhunt3r 怎么是 c 语言没学好吗?看得都不太想给你解释了。。。如果是大一学生的话希望你一定要把 c 学的扎扎实实的 虽然真正的 c 要在 linux 系统编程才能体现的淋漓精致。。
|
33
azh7138m 2019-04-15 01:22:46 +08:00
@geelaw 我翻了一下 ISO/IEC DIS 14882:2017(E)里面提到 dereferenceable 的部分似乎也没说是 0 的地方要怎么处理?
|
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. |
35
raysonx 2019-04-15 09:08:38 +08:00 via Android
很不幸的是,C 语言中 dereferencing null pointer 是未定义行为,不一定导致程序错误退出。而且如同前面其他人所说,你不能假定 malloc 返回 null 之前不会被 oom kill
|
36
bp0 2019-04-15 10:09:48 +08:00
看起来这道题的考察点是分配内存后没有检查返回值则直接使用,但是却有很多漏洞。
比如,严格说这段程序无法通过编译。因为没有包含头文件,也没有定义函数,而且循环语句无法写在文件作用域的。 如果不考虑 D。在 Linux 系统中,如果没有修改 overcommit_memory 参数,则有可能在返回 null 之前被 oomkiller 杀掉了,所以 AB 都有可能。 C 看似最不可能,但如果有无限大的内存和地址空间,为什么不行呢?虽然现实中不存在,但是题目也没说哦。 |
37
kljsandjb 2019-04-15 20:13:45 +08:00
|
38
b00tyhunt3r OP @webdisk “如果一直 malloc(1) 就会出现返回 NULL 的情况, 如果一直 malloc(1024 * 1024) 就会出现 OOM.”
一直 malloc(1)最后不也会因为 oom 而返回 null 吗?和 malloc (1024*1024)有啥区别?后者 oom 了之后不也一样会返回 null 吗 |
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 的情况. 个人觉得了解大概就行, 没必要深究这种细节, 碰到自然就明白了 |
40
b00tyhunt3r OP @webdisk 感谢!
|