答え
unsigned int
のビット幅が足りないかもしれない!
unsigned int MakeColor(unsigned char nRed, unsigned char nGreen, unsigned char nBlue) {
……
uint32_t MakeColor(uint8_t nRed, uint8_t nGreen, uint8_t nBlue) {
……
解説
プログラミングでは、変数のビット幅(その変数が何ビット分の値を格納できるか)が重要になる場面があります。問題のプログラムが期待どおりに動作するには、以下の前提条件が必要なことが分かるでしょうか。
- 三原色のそれぞれが8ビット
- カラーコードが24ビット以上(8ビットの値3つを合成するため)
三原色のほうはunsigned char
なので、まず問題ありません。カラーコードのほうも、unsigned int
はだいたいの環境では32ビットなので、問題になることは少ないでしょう。ところが、int
やunsigned int
が16ビットの環境も存在します。そのような環境では、このプログラムだと正しい計算ができません。
int
やunsigned int
のサイズって決まってないんですか?
int
やunsigned int
は、その環境でもっとも扱いやすいビット幅をもちます。これは「高速に計算できる」ことを意味するので、可能ならint
やunsigned int
を使うのが有利です。
でも、アルゴリズムが一定のビット幅を前提とするときは、int
やunsigned int
だとうまく動作しない環境が出てきてしまいます。そのような場合のために、C言語にはint32_t
やuint32_t
のようなビット幅が固定の型が用意されています。これらは<stdint.h>
をインクルードすれば使えます。
問題のプログラムでは、一定のビット幅が必要な箇所にunsigned int
とunsigned char
が使われていました。
unsigned int MakeColor(unsigned char nRed, unsigned char nGreen, unsigned char nBlue) {
unsigned int r = (unsigned int)nRed << 16;
unsigned int g = (unsigned int)nGreen << 8;
unsigned int b = (unsigned int)nBlue;
return r | g | b;
}
これは、uint32_t
とuint8_t
を使って次のように書き直すことができます。
uint32_t MakeColor(uint8_t nRed, uint8_t nGreen, uint8_t nBlue) {
uint32_t r = (uint32_t)nRed << 16;
uint32_t g = (uint32_t)nGreen << 8;
uint32_t b = (uint32_t)nBlue;
return r | g | b;
}
ただ型の名前を置き換えただけですね。
実のところ、int
やunsigned int
が32ビットの環境では、上記のように書き直しても動作は変わりません。uint32_t
とuint8_t
は、以下のような意味をもつ「別名」にすぎないためです。
typedef unsigned char uint8_t;
typedef unsigned int uint32_t;
一方、同じプログラムをint
やunsigned int
が16ビットの環境でコンパイルすると、以下のようにuint32_t
の意味が変わるということです。
typedef unsigned char uint8_t;
typedef unsigned long uint32_t;
修正後のプログラム
#include <stdlib.h>
#include <stdint.h>
#include <stdio.h>
uint32_t MakeColor(uint8_t nRed, uint8_t nGreen, uint8_t nBlue) {
uint32_t r = (uint32_t)nRed << 16;
uint32_t g = (uint32_t)nGreen << 8;
uint32_t b = (uint32_t)nBlue;
return r | g | b;
}
int main(void) {
printf("red = #%06x\n", MakeColor(255, 0, 0));
printf("blue = #%06x\n", MakeColor(0, 0, 255));
printf("magenta = #%06x\n", MakeColor(255, 0, 255));
return EXIT_SUCCESS;
}
red = #ff0000
blue = #0000ff
magenta = #ff00ff