【C言語入門】
memcpyとstrncpyの違い
~構造体内の終端文字¥0でバグるのは?~

■この記事の概要

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


■strncpyで構造体転送すると尻切れトンボになる例

#include <stdio.h>
#include <string.h>
typedef struct {
    char    *s1;
    int      i;
    char    *s2;
} tag ;
void NG(void){
    tag src = {"1234",0,"5678"};
    tag dst = {0};
    strncpy(&dst,&src,sizeof(dst));//構造体転送に不適切
    printf("[%s]%d[%s]\n",dst.s1,dst.i,dst.s2);
}
int main(void){
    NG();
}
gcc -Wall -Wextra  f1.c
f1.c: In function ‘NG’:
f1.c:11:13: warning: passing argument 1 of ‘strncpy’ from incompatible pointer type [-Wincompatible-pointer-types]
   11 |     strncpy(&dst,&src,sizeof(dst));//構造体転送に不適切
      |             ^~~~
      |             |
      |             tag *
In file included from f1.c:2:
/usr/include/string.h:144:40: note: expected ‘char * restrict’ but argument is of type ‘tag *’
  144 | extern char *strncpy (char *__restrict __dest,
      |                       ~~~~~~~~~~~~~~~~~^~~~~~
f1.c:11:18: warning: passing argument 2 of ‘strncpy’ from incompatible pointer type [-Wincompatible-pointer-types]
   11 |     strncpy(&dst,&src,sizeof(dst));//構造体転送に不適切
      |                  ^~~~
      |                  |
      |                  tag *
In file included from f1.c:2:
/usr/include/string.h:145:46: note: expected ‘const char * restrict’ but argument is of type ‘tag *’
  145 |                       const char *__restrict __src, size_t __n)
      |                       ~~~~~~~~~~~~~~~~~~~~~~~^~~~~

strncpyは文字列転送関数なので
構造体を指定すると警告がいっぱいでます。

./a.out
[1234]0[(null)]

構造体の中に0があると
strncpyは終端文字’\0’とみなして
そこで転送を終了してしまい
s2の内容が転送されません。
s2はNULLポインタのままなので未定義動作が発生します。
[(null)]と表示される保証はありません。

※’\0’は人間向けに終端文字を強調しているだけでコンパイラにとっては’\0’と0は同じです。


memcpyで構造体転送出来る

#include <stdio.h>
#include <string.h>
typedef struct {
    char    *s1;
    int      i;
    char    *s2;
} tag ;
void OK(void){
    tag src = {"1234",0,"5678"};
    tag dst = {0};
    memcpy(&dst,&src,sizeof(dst));
    printf("[%s]%d[%s]\n",dst.s1,dst.i,dst.s2);
}
int main(void){
    OK();
}
./a.out
[1234]0[5678]

memcpyを使うと0を超えて転送するので
s2もコピーされています。

構造体代入(=)でmemcpyは不要になる

#include <stdio.h>
#include <string.h>
typedef struct {
    char    *s1;
    int      i;
    char    *s2;
} tag ;
void OKOK(void){
    tag src = {"1234",0,"5678"};
    tag dst = {0};
    dst = src ;//構造体は代入できる
    printf("[%s]%d[%s]\n",dst.s1,dst.i,dst.s2);
}
int main(void){
    OKOK();
}
./a.out
[1234]0[5678]

構造体は代入(=)できるので
わざわざmemcpyを使う必要はありません。


■まとめ

●違いその1

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

●違いその2

第2引数が第3引数より短い場合、
strncpy()は残りを¥0で埋めます。
memcpy()は第2引数で領域外参照が発生して動作不明となります。

●違いその3

strncpy()に構造体のアドレスを渡すと
コンパイル時警告がいっぱいでます。


参考:

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

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