warning: conversion to ‘size_t’ from ‘short’ may change the sign of the result
警告:shortからsize_tに変換すると結果の符号が変わる可能性がある[-Wsign-conversion] [-Wconversion]
■この記事の概要
この記事では、C言語でint
より狭い型(char
やshort
)を使用する際の問題点を解説しています。符号拡張や型変換によるバグのリスク、メモリ節約の誤解を指摘し、整数型は基本的にint
を使用することを推奨。具体例を挙げて、可読性と安全性を確保するプログラミングの重要性を述べています。
■昔メモリの1byteは血の一滴
昔メモリの1byteは血の一滴とする時代があって、 整数を表現する際もint型ではなくて狭い型のchar/short型を多用していました。 今でもこの文化を継承しているプロジェクトは多々ありますが、 桁あふれや符号拡張等の面倒くさい事故にあうリスクが増えるので メモリをケチるのは止めましょう というか、狭い型のchar/short型を多用してもメモリ節約にならない事を理解しましょう。
■short型の符号拡張事故例
#include <string.h>
char buf[0x8000] ;
int main(void){
short size = 0x8000;
memset(buf,'X',size); //NG:異常終了する
}
short型で0x8000を表現できず0x8000を代入すると負値になります。
memset()の第三引数はsize_t型にもかかわらずshort型を使用したため 型変換(符号拡張)が発生し 0x8000 ではなくて 0xFFFF8000がmemset()に渡ります。
memset()に0xFFFF8000という非常に大きな値が渡ったため領域外破壊が発生し多くの場合プログラムは異常終了します。
注意: gcc -Wall -Wextra だけでは警告は出ません。[-Wsign-conversion] か[-Wconversion] を追加指定して下さい。
■char型の無限ループ事故例
#include <stdio.h>
void f(int max) {
for(char i = 0 ; i < max;i++){
printf("%d\n",i);
}
}
int main(void) {
f(256);
}
このコードは gcc -Wallでも clang -Weverythingでも警告が出ませんが char型では256を表現できないので無限ループします。
■扱う数値で型を色々使うのは避けよう
#include <stdio.h>
int main(void) {
char c;
scanf("%d",(int *)&c);
printf("コーヒーはコンビニで%d円で買える\n",c);
short s;
scanf("%d",(int *)&s);
printf("一か月の小遣いは%d円\n",s);
int i;
scanf("%d",(int *)&i);
printf("サラリーマンの生涯収入%d円",i);
}
コーヒーはコンビニで100円なのでchar型で表現可能。 月の小遣いは3万円以下なのでshort型で表現可能。 と、 最大値に応じて型をchar/short/intと様々な型を使うのは 止めましょう。
整数を扱うならば基本は int 型です。 最大値に応じて型を分けるのは無意味に几帳面でハイリスクです。 上記のコードでは”%d”書式に対応する変数はint型(4byte)必要ですがcharは1byte、shortは2byteしかありません。 1byteや2byteしかない領域に4byte書き込むのでメモリ破壊が発生してプログラムが異常終了する場合があります。
現代の32/64bitのコンパイラでは int 型は 2147483647≒21億まで表現できます。
「最大値をchar/shortで表現可能だからchar/shortを使う」 と言った貧乏くさい 発想は止めましょう。
整数を扱うならば 基本は int 型 を使用して 符号拡張、データ欠損、メモリ破壊といった事故を回避しましょう。
■整数を扱うならば基本は int 型を使う
#include <stdio.h>
int main(void) {
int c;
scanf("%d",&c);
printf("コーヒーはコンビニで%d円で買える\n",c);
int s;
scanf("%d",&s);
printf("一か月の小遣いは%d円\n",s);
int i;
scanf("%d",&i);
printf("サラリーマンの生涯収入%d円",i);
}
整数はint型が基本,無駄なキャストも削除
■除夜の鐘を数える型を検討してください
#include <stdio.h>
#include <stdint.h>
#include <stdint.h>
#if TYPE==11
//メモリの1バイトは血の一滴
//除夜の鐘は108より大きくならないのでchar型で足りる
#define TYPE_X char
#elif TYPE==12
//符号付きを明示する
#define TYPE_X signed char
#elif TYPE==13
//符号無しを明示する
#define TYPE_X unsigned char
#elif TYPE==14
#define TYPE_X int8_t
#elif TYPE==15
#define TYPE_X uint8_t
#elif TYPE==16
//基本型はtypedefする(MISRA-C 4.6)
typedef unsigned char UB;
#define TYPE_X UB
#elif TYPE==41
//除夜の鐘は負値にならない
#define TYPE_X unsigned int
#elif TYPE==42
#define TYPE_X int32_t
#elif TYPE==43
#define TYPE_X uint32_t
#elif TYPE==44
#define TYPE_X size_t
#else
//整数を扱うのに最も自然な型を使う
#define TYPE_X int
#endif
int main(void){
for(TYPE_X i = 108;i >= 0 ;i--){
printf("除夜の鐘残り%d突き\n",i);
}
}
符号無し系は無限ループして面倒な事になるし、 int より狭い型は符号拡張して面倒な事になるので 筆者は最も自然な int 型を推奨します。
参考:
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
INT02-C. 整数変換のルールを理解する
https://www.jpcert.or.jp/sc-rules/c-int02-c.html
■アンケート