【C言語】
memsetの基本的な使い方
~sizeof演算子とのよくある間違い~

warning: argument to ‘sizeof’ in ‘memset’ call is the same expression as the destination;

警告: ‘sizeof(ポインタ)’はポインタのサイズで指す先のサイズではない
[-Wsizeof-pointer-memaccess]

■この記事の概要

この記事では、C言語でのmemsetとsizeofのよくある間違いを詳しく解説しています。
特に、ポインタのサイズとポインタが指す先のサイズを混同することで発生するバグや、
sizeofの間違った使用法に起因する問題を取り上げ、
安全な修正方法を具体例とともに説明しています。
また、初期化やメモリ確保における最適解も提案しています。


■memsetの基本的使い方

#include    <stdio.h>
#include    <string.h>
typedef struct tag {
    char body[16] ;
} tag_t ;
void f(tag_t *p){
    //ポインタの指す先を全部'F'で埋める
    memset(p,'F',sizeof(*p));
}
int main(void) {
    tag_t   x;
    f(&x);
    printf("x.body=[%-16s]\n",x.body);
}
./a.out
x.body=[FFFFFFFFFFFFFFFF]

■sizeof(ポインタ)は多分*を忘れたバグ

#include    <stdio.h>
#include    <string.h>
typedef struct tag {
    char body[16] ;
} tag_t ;
void f(tag_t *p){
    //*を忘れるとポインタサイズしか設定しない
    memset(p,'F',sizeof(p));
}
int main(void) {
    tag_t   x;
    f(&x);
    printf("x.body=[%-16s]\n",x.body);
}
./a.out
x.body=[FFFFFFFF        ]

上記のsizeof(p)は
ポインタのサイズなので
4byte(ILP32bitコンパイラの場合)か
8byte(LP64bitコンパイラの場合)
にしかなりません。

文脈からプログラマが欲しいのは
ポインタのサイズではなくて
ポインタの指す先のサイズです。

■sizeof(型)は推奨しない

#include    <stdio.h>
#include    <string.h>
typedef struct tag {
    char body[16] ;
} tag_t ;
void f(tag_t *p){
    //sizeof(型)は推奨しない
    memset(p,'F',sizeof(tag_t));
}
int main(void) {
    tag_t   x;
    f(&x);
    printf("x.body=[%-16s]\n",x.body);
}

memset(p,’F’,sizeof(tag_t))と
書いてしまうと
意図した動きはしますが、
このmemset()の1行だけから
pとtag_t の関係が
読者にわかりません。


■malloc(sizeof(ptr))も*を忘れたバグ

(warning) Size of pointer ‘ptr’ used instead of size of its data.

警告:ポインタの指す先のサイズではなくポインタのサイズが使われた
[(warning)pointerSize]

#include    <stdio.h>
#include    <stdlib.h>
#include    <string.h>
typedef struct {
    char    buf[1024];
} tag_t ;
int main(void){
    tag_t *pointer = malloc(sizeof(pointer));//??
    if(pointer == NULL){
        perror(NULL);
        exit(1);
    }
    free(pointer);
}

malloc()で確保しているのは
ポインタのサイズであって
構造体のサイズではありません。
この例では
ILP32モードの時は4byte,
LP64モードの時は8byteしか
メモリを確保していないません。

参考:

EXP01-C. ポインタが参照する型のサイズを求めるのにポインタのサイズを使わない

https://www.jpcert.or.jp/sc-rules/c-exp01-c.html

■memsetで初期化するとNULLポインタになるか?

#include <stdio.h>
#include <string.h>
typedef struct tag {
    void    *p;
    double  d;
    int     i,j,k,l,m,o,q;
} tag ;

int main(void){
    tag x ;
    memset(&x,0,sizeof(x));
    puts(x.p == NULL    ?"同じ":"違う");
    puts(x.d == 0.0     ?"同じ":"違う");

    tag y ={0};
    puts(x.p == y.p     ?"同じ":"違う");
    puts(x.d == y.d     ?"同じ":"違う");
}

memsetを使って
ポインタ変数 prを0で初期化すると
ポインタ変数 p が
NULLポインタになる保証がありません。

上記のコードはほとんどの環境で”同じ”と表示されるのですが
構造体を全部0、NULL、0.0で初期化する場合は
tag y ={0}のように宣言時に={0}で初期化してください。

参考:

C-FAQ 5.17

現実にNULLポインターの内部表現が「0番地」では無いものも存在する。


■sizeof(の中にsizeof(…))

warning: suspicious usage of ‘sizeof(sizeof(…))’

警告:’sizeof(sizeof(…))’ の疑わしい使用法。
[misc-sizeof-expression]

#include <string.h>

void f(int ary[],int num){
    memset(ary,0,sizeof(num * sizeof(int)));
}

sizeofの中にsizeofがあります。

やりたい事が
int型の配列をnum個0クリアしたい場合
memset(ary,0,sizeof(num * sizeof(int)));
ではなくて
memset(ary,0,num * sizeof(int));
のはずです。


■sizeof*sizeofの掛け算

(warning): Multiplying sizeof() with sizeof() indicates a logic error.

警告:sizeof()とsizeof()の掛け算は多分間違い
[(warning)multiplySizeof]

#include    <string.h>
void NG(void){  
    char    buf[1024];
    memset(buf,0,sizeof(char)*sizeof(buf));//ダメ
}

1個のサイズ X 全体のサイズ 
= 算数的に意味がない

1個のサイズ X 全体の要素数 
= 全体のサイズ

➡修正例

#include    <string.h>
void OK(void){ 
    char    buf[1024];
    memset(buf,0,sizeof(buf)); //良い
}
void OKOK(void){  
    char    buf[1024]={0};//とても良い
}

■sizeof(定数)はsizeof不要?

(warning) Suspicious usage of ‘sizeof’ with a numeric constant as parameter.

警告:sizeof(定数)
[(warning)sizeofwithnumericparameter]

#include    <string.h>
int main(void)
{
    char    buf[1024];
    memset(buf,0,sizeof(1024));
}

この間違いをあなたはどのように
修正しますか?

memset(buf,0,sizeof(1024));//間違いの例

修正案1:memset(buf,0,1024);

修正案2:memset(buf,0,sizeof(buf));

修正案3:memset(buf,0,sizeof(char)*1024);

修正案4:char buf[1024]={0};

この問題はgccやclangでは見つけてくれません。

cppcheckと言う無料ツールが見つけてくれます。


■sizeof(配列[定数])は配列[0]の間違い?

void    f(void)
{
    char    buf[1024];
    memset(buf,0,sizeof(buf[1024]));
}

似たような間違いで
1024byte全部クリアしたいようですが、
1byteしかクリアしません。


第2引数に0x12345678は下位1byteのみ有効

#include <string.h>
#include <stdio.h>
int     main(void){
    int ary[64];  
    
    //配列を全部0x12345676で埋めたい
    memset(ary,0x12345678,sizeof(ary));
    printf("%x\n",ary[0]);
    printf("%x\n",ary[63]); 
}
./a.out
78787878
78787878

第2引数に指定できるのはint型ですが
実質unsigned charで 0~0xFFまでしか
有効でありません。

第2引数
0x12345678の上位は捨てられ
0x78の下位1byteのみ有効となります。

参考:

EXP09-C. 型や変数のサイズは sizeof を使って求める