2011-11-10 160 views
0

我想了解如何指针指针工作,我出来这个例子,它编译好。但是,当它执行时,我会遇到分段错误。 PS:我不想f1()返回char *指针在C指针

#include <stdio.h> 
#include <stdlib.h> 
#include <assert.h> 

int f1(char **str_); 

int main(int argc, char **argv) 
{ 
    char *str = NULL; 

    f1(&str);  
    printf("str : %s\n", *str); 

    str = realloc(str, (size_t) 0); 
    assert(str == NULL); 

    return 0; 
} 

int f1(char **str_) 
{ 
    if ((*str_ = realloc(*str_, sizeof(char) * 12)) == NULL) 
    { 
     fprintf(stderr,"realloc() failed\n"); 
     exit(3); 
    } 

    (*str_) = "hello there"; 

    return 0; 
} 
  • 什么是错的代码?

回答

9

您可能需要在编译器中打开更多警告或获得更好的编译器。当我编译你的代码,我得到了警告:

mem.c: In function ‘main’: 
mem.c:12: warning: format ‘%s’ expects type ‘char *’, but argument 2 has type ‘int’ 
mem.c:12: warning: format ‘%s’ expects type ‘char *’, but argument 2 has type ‘int’ 
mem.c: At top level: 
mem.c:7: warning: unused parameter ‘argc’ 
mem.c:7: warning: unused parameter ‘argv’ 
在MacOS

我不明白为什么重复上线12的警告,但两者GCC 4.2和4.6.1 X 10.7给它的两倍。当您不使用命令行参数时,关于argcargv的警告是使用int main(void)的好理由;他们不是一个大问题。

关于%sintchar *的警告(真的是错误)足以说明您的崩溃 - 但这不是代码的唯一问题。事实上,也有:

  1. 一个初期的内存泄漏,
  2. 实际泄漏,
  3. 你在哪里(意外)依赖未定义行为的地方,
  4. 一个地方,你”重新依赖实现定义的行为,这可能不符合您的期望。

您的代码注释:

#include <stdio.h> 
#include <stdlib.h> 
#include <assert.h> 

int f1(char **str_); 

int main(int argc, char **argv) 
{ 
    char *str = NULL; 

    f1(&str);  
    printf("str : %s\n", *str); 

    str = realloc(str, (size_t) 0); /* (3) Undefined behaviour */ 
    assert(str == NULL);    /* (4) Implementation-defined behaviour */ 

    return 0; 
} 

int f1(char **str_) 
{ 
    if ((*str_ = realloc(*str_, sizeof(char) * 12)) == NULL) /* (1) Incipient leak */ 
    { 
     fprintf(stderr,"realloc() failed\n"); 
     exit(3); 
    } 

    (*str_) = "hello there"; /* (2) Actual leak */ 

    return 0; 
} 

按编号顺序在讨论这些:

  1. 如果内存重新分配失败的初期泄漏发生。 *str是存储指向已分配内存的指针的前一个值的唯一位置,如果realloc()失败,则返回0(空指针),但不释放旧内存,但不再有指向旧内存的指针。

    的修复:

    char *new_mem = realloc(*str, 12); 
    if (new_mem == 0) 
        ...error handling... 
    *str = new_mem; 
    

    拇指的规则:不要realloc()的返回值分配到这是它的第一个参数变量。

  2. 由于您将指针指向指向新分配内存的指针的字符串常量,因此出现实际泄漏。最简单的解决方法是使用strcpy(),但您需要在此时添加#include <string.h>。你会还,通常情况下,要确保你分配你要复制字符串只是足够的空间,从而导致:

    char const hello[] = "hello there"; 
    char *new_mem = realloc(str, sizeof(hello)); 
    //char *new_mem = realloc(str, strlen(hello)+1); 
    if (new_mem == 0) 
        ...handle error... 
    *str = new_mem; 
    strcpy(new_mem, hello); 
    

    在这个例子中,我可以使用sizeof(hello)因为字符串的长度包括空终止,并且因为实际的数组定义在范围之内。如果要复制的字符串作为指针传递到函数中,则使用strlen(hello)+1的替代方法是正确的(并且使用sizeof()不正确),即使它需要运行时长度计算而不是编译时间计算(如图所示)。

  3. 未定义的行为是由于(2)处的内存泄漏引起的。您尝试realloc()一个字符串常量,而不是由realloc()返回的指针。这导致了未定义的行为;它可能会崩溃(因为realloc()试图将控制信息写入只读存储器),或者它可能会简化内存系统,导致一段时间后崩溃。

    对此的修正是对项目(2)的修正。

  4. 实现定义的行为的出现是因为C标准说:

    §7.20.3内存管理功能¶1:[...]如果空间的请求的大小为零,行为是实现定义的: 返回空指针,或者行为就好像大小是非零值,但返回的指针不能用于访问对象。

    您断言您的实现将选择第一个选项,但它可能会选择第二个选项。解决办法是删除无根据的断言。

所以,这是共有5个问题的代码,其中只有一个编译器可能会帮助你。

+1

很好,详细的解释各种问题。 –

+0

非常好,先生。非常感谢你,我学到了很多东西。 – pharaoh

6

main()的第一行将变量str设置为NULL,并将指针传递给f1

f1运行完好。 f1的结果是main里面的变量str现在是一个指向存储该字符串(文字)"hello there"的空间的指针。

您的下一行,printf,段错误。为什么?因为您正尝试以字符串形式(格式说明符%s)打印*str(注意星号在这里!!)。什么是*str作为字符串解释?无论“地址”用“hell”表示(至少在32位机器上)。怀疑这个地址在你的进程空间。

经典段错误。

尝试通过str而不是*strprintf

,将工作,看到http://codepad.org/Mh00txen

还有其他问题的代码,例如f1中的12个字符的realloc除了导致内存泄漏之外什么都不做,因为您立即重新指定指向字符串文本的指针,但这不是导致段错误的原因。