【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(ptr,0x00,sizeof(ptr))で初期化は多分バグ

#include    <stdio.h>
#include    <string.h>
typedef struct {
    char body[1024] ;
} tag_t ;
int main(void) {
    tag_t   x;
    tag_t   *pointer = &x;
    
    //★ダメ:ポインタのサイズでクリア?
    memset(pointer,0,sizeof(pointer));
}

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

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

●非推奨修正案 sizeof(型)

#include    <stdio.h>
#include    <string.h>
typedef struct tag {
    char body[1024] ;
} tag_t ;
int main(void) {
    tag_t   x;
    tag_t   *pointer = &x;
    
    //非推奨
    memset(pointer,0,sizeof(struct tag));
}

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

●推奨修正案 sizeof(*pointer)

#include    <stdio.h>
#include    <string.h>
typedef struct {
    char body[1024] ;
} tag_t ;
int main(void) {
    tag_t   x;
    tag_t   *pointer = &x;
    
    //ポインタが指す先のサイズでクリア
    memset(pointer,0,sizeof(*pointer));
}

ポインタの指す先のサイズが欲しい場合は
sizeof( pointer)ではなくて
sizeof(*pointer)とします。


■memset(&ptr,0,sizeof(ptr))はptr = NULLと違う?

#include    <stdio.h>
#include    <string.h>
typedef struct {
    char body[1024] ;
} tag_t ;
int main(void) {
    //変数宣言
    tag_t   variableX;
    tag_t   variableY;
    tag_t   variableZ;
    tag_t   *pointer ;
    
    //変数初期化
    memset(&variableX,  0,sizeof(variableX));
    memset(&variableY,  0,sizeof(variableY));
    memset(&variableZ,  0,sizeof(variableZ));
    memset(&pointer,    0,sizeof(pointer));//pointer=NULL?
}

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

参考:

C-FAQ 5.17

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

●修正案 ptr = NULL

#include    <stdio.h>
#include    <string.h>
typedef struct {
    char body[1024] ;
} tag_t ;
int main(void) {
    tag_t   variableX ={0};
    tag_t   variableY ={0};
    tag_t   variableZ ={0};
    tag_t   *pointer  = NULL ;
}

ポインタ自身の初期化は単純に NULL を代入して下さい。


■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しか
メモリを確保していないません。


●修正案 malloc(sizeof(*ptr))

#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(sizeof(pointer)); ではなくて
malloc(sizeof(pointer));です。

参考:

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

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

■strncmp(buf,”abc”,strlen(buf) == 0)

warning: size argument in ‘memcmp’ call is a comparison

memcmpの第三引数が比較式
[-Wmemsize-comparison]

#include    <stdio.h>
#include    <string.h>

int main(void){
    char buf[] = "abc";
    if(strncmp(buf,"abc",3 == 0)){
        puts("1来ません\n");
    }
    if(strncmp(buf,"abc",strlen(buf) == 0)){
        puts("2来ません\n");
    }
    if(memcmp(buf,"abc",sizeof(buf) == 0)){
        puts("3来ません\n");
    }
}

if(strncmp(buf,”abc”,3 == 0)){

if(memcmp(buf,”abc”,3 == 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(定数)

(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(配列[定数])

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

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


memsetの第2引数に0xFFFFFFFF

#include <string.h>
#include <stdio.h>
int     main(void){
    int ary[64];

    //配列を全部0xFFFFffffで埋めたい
    memset(ary,0xFFFFffff,sizeof(ary));
    printf("%x\n",ary[0]);
    printf("%x\n",ary[63]);    
}

➡実行結果(意図した結果になる)

./a.out
ffffffff
ffffffff

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


memsetの第2引数に0x12345678

#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引数
0x12345678の上位は捨てられ
0x78の下位1byteのみ有効となります。

参考:

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