最終更新日 2024年11月1日
warning: array subscript has type ‘char’
警告:配列の添字にchar 型
[-Wchar-subscripts]
■この記事の概要
C言語で配列の添字にchar型を使用すると、符号の影響で予期せぬ動作が発生します。
この記事は、添字にint型を使用する理由と効率的なコードの書き方を初心者向けに解説します。
■配列の添字に不適切なchar型
#include <stdio.h>
char buf[255]={0};
int main(void){
char idx = 128;
buf[idx] = 'A';
printf("buf[%d]=%c\n",idx,buf[idx]);
}char型は
符号付きか、
符号無しか
コンパイラに依存するので
配列の添字にchar使うのは不適切です。
char が符号無しの時
添字は0~255までしか参照できません。
char が符号付きの時
添字は-128~127までしか参照できません。
※正確にはご自身の環境のCHAR_MIN/CHAR_MAXを参照ください。
■添字にキャストする(非推奨)
#include <stdio.h>
char buf[255]={0};
int main(void){
char idx = 128;
buf[(signed char)idx] = 'A';
printf("buf[%d]=%c\n",idx,buf[(unsigned char)idx]);
}警告は消えますが、
gccで異常終了しました。
型を見直さないでとにかく
キャストするのは悪い習慣です。
■添字にsigned char型(非推奨)
#include <stdio.h>
char buf[255]={0};
int main(void){
signed char idx = 128;
buf[idx] = 'A';
printf("buf[%d]=%c\n",idx,buf[idx]);
}signed char型を使うと警告は消えますが、
gccで異常終了しました
■添字にunsigned char型(非推奨)
#include <stdio.h>
char buf[255]={0};
int main(void){
unsigned char idx = 128;
buf[idx] = 'A';
printf("buf[%d]=%c\n",idx,buf[idx]);
}unsigned char型を使うと警告は消えますが、
効率の悪いコードが生成される可能性があります。
■添字にint型(推奨)
#include <stdio.h>
char buf[255]={0};
int main(void){
int idx = 128;
buf[idx] = 'A';
printf("buf[%d]=%c\n",idx,buf[idx]);
}物を数えるのに自然なint型を使うと
警告は消え、意図した動作をします。
■配列の添字にint型より狭いcharやshortを使うと
メモリサイズが増える!?
メモリの1byteは血の一滴と言われた時代の影響か
令和の時代の今でもメモリ節約と称して
int型より狭い型(char/short型)を
多用する古い習慣がまだあります。
昔のANSI-C非準拠の組み込みマイコン用コンパイラでは狭い型(char/short型)を多用するとメモリ節約になったようですが
現代のコンパイラではどうでしょうか?
実験してみましょう。
■比較実験用ソースコード
#include <stdio.h>
typedef unsigned char U1 ;
typedef unsigned int U4 ;
void MYmemset(void *p,int x,size_t size)
{
U1 *buf = p ;
for(TYPE i = 0;i < size;i++){
buf[i] = x;
}
}
int main(void)
{
char buf[1024];
MYmemset(buf,0xFF,sizeof(buf));
printf("%c%c\n",buf[0],buf[1023]);
}■比較実験用SHELLスクリプト
#スタックサイズを調べる(最適化無し)
gcc MYmemset.c -DTYPE=U1 -c -Wframe-larger-than=0
gcc MYmemset.c -DTYPE=U4 -c -Wframe-larger-than=0
#スタックサイズを調べる(最適化有り)
gcc MYmemset.c -DTYPE=U1 -c -Wframe-larger-than=0 -O
gcc MYmemset.c -DTYPE=U4 -c -Wframe-larger-than=0 -O
#TEXT/DATA/BSSサイズを調べる
gcc MYmemset.c -DTYPE=U1 -c -O
size MYmemset.o
gcc MYmemset.c -DTYPE=U4 -c -O
size MYmemset.o
exit実験用SHELLスクリプト
(1) gcc -Wframe-larger-than=0 でスタックサイズを調べる
(2) size コマンドで TEXT/DATA/BSSのサイズを調べる
➡実験結果:U4型よりU1型のほうがサイズが増えた
+ gcc MYmemset.c -DTYPE=U1 -c -Wframe-larger-than=0
MYmemset.c: In function ‘MYmemset’:
MYmemset.c:10:1: warning: the frame size of 40 bytes is larger than 0 bytes [-Wframe-larger-than=]
10 | }
| ^
MYmemset.c: In function ‘main’:
MYmemset.c:16:1: warning: the frame size of 1040 bytes is larger than 0 bytes [-Wframe-larger-than=]
16 | }
| ^
+ gcc MYmemset.c -DTYPE=U4 -c -Wframe-larger-than=0
MYmemset.c: In function ‘MYmemset’:
MYmemset.c:10:1: warning: the frame size of 40 bytes is larger than 0 bytes [-Wframe-larger-than=]
10 | }
| ^
MYmemset.c: In function ‘main’:
MYmemset.c:16:1: warning: the frame size of 1040 bytes is larger than 0 bytes [-Wframe-larger-than=]
16 | }
| ^
+ gcc MYmemset.c -DTYPE=U1 -c -Wframe-larger-than=0 -O
MYmemset.c: In function ‘main’:
MYmemset.c:16:1: warning: the frame size of 1040 bytes is larger than 0 bytes [-Wframe-larger-than=]
16 | }
| ^
+ gcc MYmemset.c -DTYPE=U4 -c -Wframe-larger-than=0 -O
MYmemset.c: In function ‘main’:
MYmemset.c:16:1: warning: the frame size of 1040 bytes is larger than 0 bytes [-Wframe-larger-than=]
16 | }
| ^
+ gcc MYmemset.c -DTYPE=U1 -c -O
+ size MYmemset.o
text data bss dec hex filename
224 0 0 224 e0 MYmemset.o
+ gcc MYmemset.c -DTYPE=U4 -c -O
+ size MYmemset.o
text data bss dec hex filename
223 0 0 223 df MYmemset.o
+ exit(1)ループカウンタがU1型の時もU4型の時も
共にスタックサイズは40byte。
※-Oで最適化するとスタックサイズは共に0になりました
(レジスタに割り付けられた模様)
(2)textサイズ(=ROMサイズ)は
U1型の時224byte
U4型の時223byte
■注意事項
(1) スタックサイズは
コンパイラの種類や最適化の有無で変わります。
(2) このプログラムは
TYPEがU1型の時無限ループします。
U1型では1024を表現できず無限ループしますが、
gcc/clangの警告では検出できません。
(3) 一般的に狭い型の演算ではint型への型変換が発生するため
この分ROMサイズが増加し処理速度も劣化します。
参考:
http://www.kouno.jp/home/c_faq/c1.html#1
https://www.ipa.go.jp/security/awareness/vendor/programmingv2/contents/c907.html