👋

【C言語】memchr関数のunsigned charを理解する

2023/02/05に公開

きっかけ

memchr関数を自作しようとしたところ、再現にはunsigned charの理解が必要だったため、いろいろ試した過程をまとめました。、

memchr関数の定義は以下です。

void *memchr(const void *s, int c, size_t n);

メモリ内の文字が、引数cと同じか比較する関数です。
 文字はASCIIでは10進数の場合、0(NULL)~127(DEL)までですが、引数cをマイナスにした場合や、*sに0~127を入れたらどうなるでしょうか。

上記のような、ほぼありえなさそうなケースも念のためテストしたところ、結果的に再現できていない箇所を発見できました。その原因がunsigned charという型でした。

元の自作関数(再現できてなかった)

再現できていないことが発覚したときのコードは以下です。

test.c
void	*mymemchr(const void* s, int c, size_t n)
{
	unsigned char	*str;
	str = (unsigned char *)s;
	if (str == NULL)
		return (NULL);
	while (n--)
	{
		if (*str == c)
			return (str);
		str++;
	}
	return (NULL);
}

int main(void){
	unsigned char s[] = "abcdefgh";
	char *p1;
	char *p2;
	int c =-120;
	int n = 8;
	s[2]=-120;		//文字列sの'c'を-120に変更
	p1 = memchr(s,c, n);
	printf("memchr   = %s\n",p1);
	p2 = mymemchr(s,c, n);
	printf("mymemchr = %s\n",p2);
	return 0;
}

実際の関数、自作した関数の結果は以下のとおり、異なっていました。

memchr   = �defgh
mymemchr = (null)

再現できてなかった原因

原因は引数cをint型のままにしていたことでした。

自作前の下調べで、「memchr関数は引数s,cをunsigned charとして比較する」と見た記憶がありました。unsigned charの理解を後回しにした自覚があったので原因特定は早かったです。

原因特定のための確認用コードは以下です。s,cの値が自作コードの中でどのようにな挙動か確認します。

test.c
void	*mymemchr(const void* s, int c, size_t n)
{
	unsigned char	*str;
	str = (unsigned char *)s;
	printf("str[2]=%d, c=%d\n",str[2],c);   //確認用コードを追加
	if (str == NULL)
		return (NULL);
	while (n--)
	{
		if (*str == c)
			return (str);
		str++;
	}
	return (NULL);
}

//mainは同じ

確認用コードの結果は以下のとおりでした。

str[2]=136, c=-120

unsigned char型のstr[2]では、代入した-120は136に変わっていました。

unsigned charの範囲は、0~255です。
-120は136に変わった理由は、オーバーフロー?したためのようです。つまり、以下のような挙動を示します。

unsigned char *s=-1    // s=255
unsigned char *s=-2    // s=254
unsigned char *s=-120  // s=136

解決方法

引数cを、論理和(&演算子)を使ってunsigned charと同じ0~255の範囲にします。

int main()
{
	int a,b,c,d,e;
	a = 0;
	a &= 255;
	printf("a=%d\n",a);
	b = -120;
	b &= 255;
	printf("b=%d\n",b);
	c = 255;
	c &= 255;
	printf("c=%d\n",c);
	d = 450;
	d &= 255;
	printf("d=%d\n",d);
	e = -4450;
	e &= 255;
	printf("e=%d\n",e);

	return(0);
}

結果は以下のとおりです。

a=0
b=136
c=255
d=194
e=158

修正後の自作関数

test.c
void	*mymemchr(const void* s, int c, size_t n)
{
	unsigned char	*str;

	c &= 0xff;
	str = (unsigned char *)s;
	printf("str[2]=%d, c=%d\n",str[2],c);  //確認用コード残し
	if (str == NULL)
		return (NULL);
	while (n--)
	{
		if (*str == c)
			return (str);
		str++;
	}
	return (NULL);
}
//mainは同じ

結果は以下のとおりです。

memchr   = �defgh
str[2]=136, c=136
mymemchr = �defgh

引数s、cが想定外の数値でも再現ができるようになりました。

参考にしたサイト

https://hiroyukichishiro.com/memchr-function-in-c-language/
https://wa3.i-3-i.info/word11663.html
https://qiita.com/mizcii/items/a5adb7a56b1c4a31951a

Discussion