第16問の答え

答え

環境によっては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ビットなので、問題になることは少ないでしょう。ところが、intunsigned intが16ビットの環境も存在します。そのような環境では、このプログラムだと正しい計算ができません。

え?intunsigned intのサイズって決まってないんですか?
そうよ。
決まっていれば簡単なのに……。なんでそうなってないんです?
それは、扱いやすいサイズが環境ごとに違うからね。

intunsigned intは、その環境でもっとも扱いやすいビット幅をもちます。これは「高速に計算できる」ことを意味するので、可能ならintunsigned intを使うのが有利です。

でも、アルゴリズムが一定のビット幅を前提とするときは、intunsigned intだとうまく動作しない環境が出てきてしまいます。そのような場合のために、C言語にはint32_tuint32_tのようなビット幅が固定の型が用意されています。これらは<stdint.h>をインクルードすれば使えます。

問題のプログラムでは、一定のビット幅が必要な箇所にunsigned intunsigned 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_tuint8_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;
}

ただ型の名前を置き換えただけですね。

実のところ、intunsigned intが32ビットの環境では、上記のように書き直しても動作は変わりません。uint32_tuint8_tは、以下のような意味をもつ「別名」にすぎないためです。

typedef unsigned char uint8_t;
typedef unsigned int uint32_t;

一方、同じプログラムをintunsigned intが16ビットの環境でコンパイルすると、以下のようにuint32_tの意味が変わるということです。

typedef unsigned char uint8_t;
typedef unsigned long uint32_t;
ここがポイント!
ビット幅が意味をもつプログラムを作るときは、ビット幅が固定の型を使おう!

修正後のプログラム

main.c
#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