首頁  >  問答  >  主體

c++ - 为什么这个程序会崩溃

void GetMemory(char *p)
{
    p = (char *)malloc(100);
}
void Test(void) 
{
    char *str = NULL;
    GetMemory(str);   
    strcpy(str, "hello world");
    printf(str);
}
大家讲道理大家讲道理2713 天前530

全部回覆(3)我來回復

  • PHP中文网

    PHP中文网2017-04-17 11:12:58

    提问者似乎不太了解 C 语言,建议阅读《K & R》等基础教程。

    变量赋值错误(崩溃原因)

    你试图通过 GetMemory()*str 指向一段 malloc() 分配的内存。

    但是,C 语言的函数传参是按值传参,*str 的值 NULL 被拷贝到 *p,随后 *p 被指向 malloc() 返回的值,此时 GetMomory() 函数结束。*str 的值仍然为 NULL,没有任何变化。之后,你使用 strcpy 企图把 "hello world" 复制到 NULL,当然出错。

    正确的做法是,传递指针变量 *str 的地址给 GetMemory(char **p)*str 本身就是一个指针,指向一个地址。*str 虽然是指针,但指针也有自己的地址,把 *str 的地址传给 **p**p 便是一个指向指针的指针。

    到此为止,你的主要问题已经解决。可以跳过剩余部分直接看代码

    遗漏 free()

    程序退出后,操作系统会自动释放所有内存。但是,使用完内存后立刻释放永远没错。

    printf() 用法错误

    另外,你的 printf() 用法错误。printf() 的参数不能直接是要打印的数据。而需要一个带转换说明符的字面字符串,后面的参数是要格式化的实参。

    有人说按照 printf() 的原型,直接跟一个字符串 printf(str) 也是没有问题的。然而,str 如果是从外部获得的内容,如用户输入的,会导致严重的安全问题。GCC 和 Clang 会对这样的用法都会产生警告。

    比如说,在老旧的 32 位 Linux 上:

    #include <stdio.h>
    
    int main(int argc, char **argv)
    {
        char *secret = "This is a secret!\n";
        printf(argv[1]);
        return 0;
    }
    

    如果使用 -mpreferred-stack-boundary=2 编译

    $ gcc -mpreferred-stack-boundary=2 FormatString.c -o FormatString
    $ ./FormatString %s
    This is a secret!
    

    哦?这是为什么?我对底层了解不多,只能说这和 varargs 的内存越界有关,而 *secret 正好在这个内存地址上。有没有联想到 OpenSSL 前些时间曝光了“心脏出血”漏洞?

    -mpreferred-stack-boundary=2 并非攻击的必要手段。只要条件适宜,经过周密计算,危害可能比这大得多。

    因此这样的用法必须禁止,应使用

    printf("%s\n", str);
    

    puts(str);
    

    没有 #include 任何头文件

    使用库函数前需要 #include 提供相应函数的头文件。

    • printf()stdio.h 提供
    • malloc()stdlib.h 提供
    • strcpy()string.h 提供

    没有 main() 函数

    main() 函数是程序的入口,没有入口,程序从何处执行?


    更正后的程序如下。

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    
    void GetMemory(char **p)
    {
        *p = (char *)malloc(100);
    }
    
    void Test(void) 
    {
        char *str = NULL;
        GetMemory(&str);   
        strcpy(str, "hello world");
        printf("%s\n", str);
    
        free(str);
        *str = NULL; // 避免误用已悬空的指针
    }
    
    int main(void)
    {
        Test();
        return 0;
    }
    

    回覆
    0
  • 天蓬老师

    天蓬老师2017-04-17 11:12:58

    娘的……答案评论里不支持贴代码,那我就另外加一个答案吧。对于最佳答案的一些评论

    1. printf 的原型是
    int printf ( const char * format, ... );
    

    因此,printf(str) 是可行的。

    但是,如果 str 是用户输入的内容或外部获得的内容,str 中就可能含有恶意代码,可以用来进行内存溢出进而执行任意代码的攻击。因此,这样做应该是严格禁止的。—— Biergaizi

    1. c/c++对于申请的内存空间进行初始化是一个好习惯,所以做一个memset或者是把第一个单元初始化为零是值得鼓励的做法
    2. 另外像楼上说的,malloc的内存要记得free,否则一会就没有内存可以用了。同时记得free之后把指针清零。避免代码过长之后,错误调用这个已经free的指针
    3. c中的字符串是必须以0结尾的。这里你用的strcpy在复制字符串的时候帮你做了这件事情。但是你要心里有数。
    4. 你的GetMemory可以直接返回一个指针,就免去传递指针的指针这个问题了
    char* GetMemory()
    {
       char* p = (char *)malloc(100);
       memset(p, 0, SIZE); // or *p = 0;
       return p;
    }
    

    以下下代码在win7上编译,运行通过

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #define SIZE 100
    void GetMemory(char **p)
    {
       *p = (char *)malloc(
       memset(*p, 0, SIZE); // or **p = 0;
    }
    
    void Test(void) 
    {
        char *str = NULL;
        GetMemory(&str);   
        strcpy(str, "hello world");
        printf(str);
        free( str);
        str = NULL;
    }
    
    int main(void)
    {
        Test();
        return 0;
    }
    

    回覆
    0
  • 黄舟

    黄舟2017-04-17 11:12:58

    最简单的回答就是楼主没有搞清楚: 引用传递和值传递

    想要修改str指向的内容,你得把指向str的指针传过去

    回覆
    0
  • 取消回覆