【C言語】
配列の添字に文字型charを避け安全で効率的な整数型intを使おう

配列の添字

warning: array subscript has type ‘char’

警告:配列の添字にchar 型
[-Wchar-subscripts]


■配列の添字に不適切な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サイズが増加し処理速度も劣化します。

参考:

C-FAQ 1.1: 予想不能な符号拡張の問題とコード サイズの増加

http://www.kouno.jp/home/c_faq/c1.html#1

int よりも狭いビット幅の型の多用を避ける。

https://www.ipa.go.jp/security/awareness/vendor/programmingv2/contents/c907.html

■アンケート

10
配列の添え字によく使う型を教えてください