【C言語】
strdup()の基本的な使い方
~使用上の注意と自作時の注意~

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関数を使用した自作の具体例や、
適切な修正方法が紹介しています。


strdupの基本的な使い方

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int main (int argc,char *argv[]) {
    char *my_argv[100] ;
    for(int i = 0; i < argc; i++){
        char buf[1024];
        sprintf(buf,"argv[%d] = %s",i,argv[i]);
        my_argv[i] = strdup(buf);
    }
    for(int i = 0;i < argc; i++){
        puts(my_argv[i]);
    }
}
./a.out 1 23 456 7890
argv[0] = ./a.out
argv[1] = 1
argv[2] = 23
argv[3] = 456
argv[4] = 7890

コマンドラインのargvを
sprintf で編集し、
出力の文字列をstrdupで複写し
my_argv[]に引っかけています。

このサンプルでは、以下2点の処理が不足しています。
(1) strdupの戻り値のNULLチェック
(2) strdupで確保した領域の解放


NULLチェックと解放処理を忘れない

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int main (int argc,char *argv[]) {
    char *my_argv[100] ;
    for(int id = 0; id < argc; id++){
        char buf[1024];
        sprintf(buf,"argv[%d] = %s",id,argv[id]);
        char *cp = strdup(buf);
        if(cp == NULL){ //NULLチェックをする
            perror(NULL);
            exit(1);//解放はOSに任せるC-FAQ7.24
        }
        my_argv[id] = cp ;
    }
    for(int id = 0;id < argc; id++){
        puts(my_argv[id]);
        free(my_argv[id]);//解放する
    }
}

NULLチェックと解放処理を追加した例です。
strdup()は中でmalloc()を呼んでいるので
malloc()に失敗するとNULLを返します。
又、
解放しないとメモリリークしてしまいます。


strdupを自作する場合の注意事項+1

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
char *NG_strdup(char *cp){
    char *mp = malloc(strlen(cp)) ;
    if(mp == NULL){
        return NULL;
    }
    return strcpy(mp,cp);
}
char *OK_strdup(char *cp){
    char *mp = malloc(strlen(cp)+1) ;
    if(mp == NULL){
        return NULL;
    }
    return strcpy(mp,cp);
}
int main (void) {
    printf("OK=%s\n",OK_strdup("OK:終端文字考慮"));
    printf("NG=%s\n",NG_strdup("NG:終端文字分忘れ"));
}
$ gcc -Wall -Wextra  f3-my_strdup.c  -fsanitize=address
$ ./a.out
OK=OK:終端文字考慮
中略
==12650==ERROR: AddressSanitizer: 
heap-buffer-overflow on address 
後略

strdupを自作する場合は
malloc(strlen(func)+1)の1を忘れないで下さい。
忘れると終端文字\0の分が無いのでメモリ破壊が発生します。

実行しても異常終了しない場合は
問題が潜在化しているので
gcc -fsanitize=address -g
でコンパイル実行してみて下さい。


malloc使用時しなくて良い事

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
char *KISS_strdup(char *cp){
    size_t  len = strlen(cp);
    char    *mp = (char *)malloc(sizeof(char) * (len + 1)) ;
    if(mp == NULL){
        return NULL;
    }
    strcpy(mp,cp);
    return mp;
}
int main (void) {
    char *cp = KISS_strdup("Keep it simple stupid.");
    printf("KISSの原則=%s\n",cp);
    free(cp);
}

(1) (char *)mallocと戻り値をキャストする必要はありません。
#include <stdlib.h>を忘れてキャストすると事故る場合があります。
コンパイルエラーになってしまう人は多分C++コンパイラを起動しています。
C++を気にする人はnewを使いましょう。
(2)sizeof(char)はC言語仕様で1と決まっているので明示的に掛け算する必要はありません。


参考:

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

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