【C言語】
strNcpyとmemcpyの違いは
終端文字¥0


■この記事の概要

この記事では、C言語のstrncpymemcpyの違いを解説しています。strncpyは終端文字\0を含めた文字列操作を行い、不足部分を埋めますが、memcpyは終端文字を考慮せず指定サイズを転送します。誤用によるバグや安全な使い方について、具体例を交えて説明しています。


■strNcmpとmemcmpが同じ例

#include <stdio.h>
#include <string.h>
char s1[] = "abc" ;  
char s2[] = "abc" ;
int main(void){
    if(strncmp(s1,s2,sizeof(s1)) == 0){
        puts("同じ:ここに来る");
    } else {
        puts("違う:来ない");
    }
    if(memcmp(s1,s2,sizeof(s1)) == 0){
        puts("同じ:ここに来る");
    } else {
        puts("違う:来ない");
    }
}

同じ動きをする時もあります。


■strNcmpとmemcmpが違う例

#include <stdio.h>
#include <string.h>
char s1[] = "abc\0DEF" ;//abc+DEF 
char s2[] = "abc\0XYZ" ;//abc+XYZ
int main(void){
    if(strncmp(s1,s2,sizeof(s1)) == 0){
        puts("同じ:ここに来る");
    } else {
        puts("違う:来ない");
    }
    if(memcmp(s1,s2,sizeof(s1)) == 0){
        puts("同じ:来ない");
    } else {
        puts("違う:ここに来る");
    }
}

●違いその1

strncmp()は終端文字¥0までしか比較しない
memcmp()は終端文字¥0を超えて比較する

●違いその2

strncmp()の第1,第2引数には char * 型しか渡せません。
(キャストしてコンパイラを騙し警告を無視するのは論外とします)
memcmp()の第1,第2引数はvoid *型なのでデータのアドレスならば
何でも渡せます。

●実行結果

./a.out 
同じ:ここに来る
違う:ここに来る

■strNcpyとmemcpyが違う例

//gcc  -g  f1.c -fsanitize=address
#include <stdio.h>
#include <string.h>
static char コピー元小[8] = "abc\0xyz";
static char コピー先大[16];         
int main(void){
    //'\0'を終端とするstr系
    strncpy(コピー先大,コピー元小,sizeof(コピー先大));
    printf("%s:%s\n",&コピー先大[0],&コピー先大[4]);

    //'\0'を終端としないmem系(バグあり)
    memcpy(コピー先大,コピー元小,sizeof(コピー先大));//★
    printf("%s:%s\n",&コピー先大[0],&コピー先大[4]);
}

●違いその1

strncpy()は終端文字¥0までしか転送しません。
memcpy()は終端文字¥0を超えて指定のサイズまで転送します。

●違いその2

第2引数が第3引数より短い場合、

strncpy()は残りを¥0で埋めます。

memcpy()は第2引数で領域外参照が発生して動作不明となります。

※このプログラムの例では転送元は8byteしかないのに16byte読み込むので領域外参照のバグとなります。
gcc -g -fsanitize=address でコンパイルすると異常終了しました。


■文字列比較にmemcmpを使ってバグった例

#include <stdio.h>
#include <string.h>
int main(void){
    char    str1[1024]="ABCD";
    char    str2[1024];
    strcpy(str2,"ABCD");

    puts("memcmp");
    if(memcmp(str1,str2,sizeof(str1)) == 0){
        printf("%sと%sは同じ\n",str1,str2);
    }else{
        printf("%sと%sは違う\n",str1,str2);
    }

    puts("strncmp");
    if(strncmp(str1,str2,sizeof(str1)) == 0){
        printf("%sと%sは同じ\n",str1,str2);
    }else{
        printf("%sと%sは違う\n",str1,str2);
    }
}

(1)str1[1024]は先頭の”ABCD”と残り全部が0保証されます。
(2)str2[1024]は先頭の”ABCD”と終端文字の\0が保証されますが、
残りは全部ゴミの値です。
(3)memcmpは1024byte全部比較するので
ゴミの内容で結果が変わります。
(4)strncmpは先頭5byte(ABCDと終端文字\0)を比較して終了するのでstr1とstr2は同じ文字列となります。

➡str1,str2のサイズ1024を小さくしてみましょう。
結果がコロコロ変わる
非常に恐ろしいバグと分るでしょう。

参考:

C-FAQ 13.2:任意のバイトをコピーするときには、普通は memcpy()のほうがstrncpy()よりも適切なルーチンである。

http://www.kouno.jp/home/c_faq/c13.html#2