一道C选择题



 void Test(void)
{
    char *str = (char *) malloc(100);
    strcpy(str, “hello”);
    free(str);
    if (str != NULL)
    {
        strcpy(str, “world”);
        printf(str);
    }
}

请问运行Test函数会有什么样的结果?


 hello
world
helloworld
未定义行为

c

阿婆你太给力了 8 years, 6 months ago

看到汇编层面剖析的问题的好厉害。

faltering answered 8 years, 6 months ago

你用你的电脑运行几乎所有输出都是 world 但是答案我觉得应该是 未定义

free(str) 释放 str 指向的那段内存, free 并不会把 str 赋值为 NULL
并不是说这段内存不存在了,只是告诉操作系统我这一段内存我这个程序不再使用。
所以当你再访问这段内存的时候他还是存在的,同时也会被操作系统捕获一个异常(现代操作系统,古老的就没有了)。

Saorika answered 8 years, 6 months ago

d str已经被释放了

撿肥皂的基佬 answered 8 years, 6 months ago

虽然不是 焦点访谈 ,但也可以用实事说话..
有图有真相:

clipboard.png

但是, 当我把编译好的程序放在 调试器 里调试的时候, 情况就又不一样了.
反汇编后的汇编代码:


 Address   Hex dump          Command                                  Comments                               Label
01091000  /$  55            PUSH EBP
01091001  |.  8BEC          MOV EBP,ESP
01091003  |.  51            PUSH ECX
01091004  |.  6A 64         PUSH 64                                  /Arg1 = 64 (这里的 64是16进制, 10进制对应的是100)
01091006  |.  E8 5D020000   CALL 01091268                            \malloc 
0109100B  |.  83C4 04       ADD ESP,4
0109100E  |.  8945 FC       MOV DWORD PTR SS:[LOCAL.1],EAX           /将返回值保存
01091011  |.  68 00B00901   PUSH OFFSET 0109B000                     ASCII "hello"
01091016  |.  8B45 FC       MOV EAX,DWORD PTR SS:[LOCAL.1]
01091019  |.  50            PUSH EAX
0109101A  |.  E8 51010000   CALL 01091170                            /strcpy(str, "hello")
0109101F  |.  83C4 08       ADD ESP,8
01091022  |.  8B4D FC       MOV ECX,DWORD PTR SS:[LOCAL.1]
01091025  |.  51            PUSH ECX                                 /Arg1 => [LOCAL.1]
01091026  |.  E8 FE000000   CALL 01091129                            \free
0109102B  |.  83C4 04       ADD ESP,4
0109102E  |.  837D FC 00    CMP DWORD PTR SS:[LOCAL.1],0             /拿 LOCAL.1 和 0 进行比较
01091032  |.  74 1D         JE SHORT 01091051                        \如果相等, 则跳(即 if(str == NULL)
01091034  |.  68 08B00901   PUSH OFFSET 0109B008                     ASCII "world"
01091039  |.  8B55 FC       MOV EDX,DWORD PTR SS:[LOCAL.1]
0109103C  |.  52            PUSH EDX
0109103D  |.  E8 2E010000   CALL 01091170                            /strcpy(str, "world")
01091042  |.  83C4 08       ADD ESP,8
01091045  |.  8B45 FC       MOV EAX,DWORD PTR SS:[LOCAL.1]
01091048  |.  50            PUSH EAX
01091049  |.  E8 1E000000   CALL 0109106C                            /printf(str)
0109104E  |.  83C4 04       ADD ESP,4
01091051  |>  8BE5          MOV ESP,EBP
01091053  |.  5D            POP EBP
01091054  \.  C3            RETN

call 之前

clipboard.png

call 之后

clipboard.png

可以看到它并未变成 NULL , 所以后面的 JE 并不会跳, 从而会将 world 复制过去, 然后输出的结果为 world .

在楼主所提供的四个答案中, hello helloworld 可以被排除, 那么只剩下 world 未定义行为 这两个 选项了, 而上面的例子又可以证明它有时候会输出 world , 而有时候又不会输出 world , 所以选择 world 也是不太恰当的, 那么就只剩下 未定义行为 这个答案了.

附在 linux 下运行的结果:

clipboard.png

战场原灰原哀 answered 8 years, 6 months ago

选择world。

请叫我寂寞君 answered 8 years, 6 months ago

最典型的未定义行为之一

遥远D星空 answered 8 years, 6 months ago

上面用汇编确实好厉害呀,但是不一定都看得懂。从C语言层分析的话,应该是这样的。


 char * str = malloc(100);

malloc分配的内存指针赋给str,这段内存并未初始化,所以是非NULL的。


 strcpy(str, "hello");

将"hello"拷贝到str指向的内存


 free(str);

释放str指向的内存,但内存被释放后,很有可能该指针仍然指向该内存单元,此时这段内存已经不再属于这个程序,str也就是一个非NULL的 悬空指针 了。

后续的 strcpy(str, "world"); 也就是非法操作了!所以是未定义行为。

O4LOVE answered 8 years, 6 months ago

Your Answer