【C言語】
mallocでヌル文字を考慮したメモリ確保
– strlenによるバグ対策-

warning: ‘strcpy’ writing one too many bytes into a region of a size that depends on ‘strlen’

警告:strlen()で確保した領域は終端文字’¥0’の考慮が足りない
[-Wstringop-overflow=]


■この記事の概要

この記事では、C言語でmalloc(strlen)を使う際、終端文字(ヌル文字)を考慮しないとメモリ破壊が起こる問題について解説しています。

strcpy関数を使用した具体例や、適切な修正方法が紹介されています。


終端文字’¥0’の1byte足りない(heap overflow)

//gcc -fsanitize=address -g で潜在バグが表面化する
void I_hate_bug(void){
    char *str = malloc(strlen(__func__));
    if(str == NULL){
        perror(NULL);
        return  ;
    }
    strcpy(str,__func__);
    puts(str);
    free(str);
}

例えば
char *str = malloc(strlen(“abc”)); 
strlen(“abc”)の結果は3となり
終端文字’¥0’の分は
malloc()で確保されません。

strcpy()は終端文字’¥0’までコピーするので
strの領域は1byte足りずメモリ破壊されます。

熱心すぎる修正例(非推奨)

void I_love_casts(void){
    char *str ; 
    size_t len = strlen(__func__)+sizeof('\0');
    str = (char *)malloc(sizeof(char)*len);
    if((char *)NULL == str){
        perror(NULL);
        return  ;
    }
    strcpy(str,__func__); 
    puts(str);
    free(str);
}

(1)C言語では(char *)mallocのキャストが不要です。
C++とは違います。
(2)C言語ではsizeof(‘\0’)の結果は1ではありません。
C++とは違います。
(3)C言語ではsizeof(char)の結果は1と決まっているので掛け算は不要です。
(4)NULLにキャストは不要です。

参考

http://www.kouno.jp/home/c_faq/c7.html#7

http://www.kouno.jp/home/c_faq/c7.html#8

●推奨修正例1

void I_hate_casts(void){
    char *str = malloc(strlen(__func__)+1);
    if(str == NULL){
        perror(NULL);
        return  ;
    }
    strcpy(str,__func__);
    puts(str);
    free(str);
}

●推奨修正例2(strdupを使う)

void I_love_Simple(void){
    char *str = strdup(__func__);//標準関数を使う
    if(str == NULL){
        perror(NULL);
        return  ;
    }
    strcpy(str,__func__);
    puts(str);
    free(str);
}

malloc(strlen(func)+1)の1が
マジックナンバーで気になる人は
標準関数strdup()を使うと良いでしょう。


■サンプルコードのまとめ

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
//終端文字’¥0’の1バイト足りない
//gcc -fsanitize=address -g で潜在バグが表面化する
void I_hate_bug(void){
    char *str = malloc(strlen(__func__));
    if(str == NULL){
        perror(NULL);
        return  ;
    }
    strcpy(str,__func__);
    puts(str);
    free(str);
}
//熱心すぎる修正例(非推奨)
//(1)C言語では(char *)mallocのキャストが不要。C++と違う
//(2)C言語ではsizeof('\0')の結果は1ではない。C++と違う
//(3)C言語ではsizeof(char)の結果は1と決まっているので掛け算不要
//(4)NULLにキャスト不要
void I_love_casts(void){
    char *str ; 
    size_t len = strlen(__func__)+sizeof('\0');
    str = (char *)malloc(sizeof(char)*len);
    if((char *)NULL == str){
        perror(NULL);
        return  ;
    }
    strcpy(str,__func__); 
    puts(str);
    free(str);
}
//修正例   
void I_hate_casts(void){
    char *str = malloc(strlen(__func__)+1);
    if(str == NULL){
        perror(NULL);
        return  ;
    }
    strcpy(str,__func__);
    puts(str);
    free(str);
}
//推奨修正例   
void I_love_Simple(void){
    char *str = strdup(__func__);//標準関数を使う
    if(str == NULL){
        perror(NULL);
        return  ;
    }
    strcpy(str,__func__);
    puts(str);
    free(str);
}
int main(void){
    I_hate_bug();
    I_love_casts();
    I_hate_casts();
    I_love_Simple();
}

コンパイル実行しても異常終了しない場合は
gcc -fsanitize=address -g
でコンパイル実行してみて下さい。

アンケート

3
10文字の文字列領域をmallocで確保するのに最善の方法はどれですか?