搜尋

首頁  >  問答  >  主體

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

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

全部回覆(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
  • 取消回覆