第18問の答え

答え

構造体のサイズを、メンバーのサイズの合計だと考えているのが間違い!
    AlphabetInfo *info = malloc(5);
こう書いても結果は同じなので、やっぱり間違い!
    AlphabetInfo *info = malloc(sizeof(uint8_t) + sizeof(uint32_t));
構造体のサイズは全体で表すのが正解!
    AlphabetInfo *info = malloc(sizeof(AlphabetInfo));

解説

これは、「アラインメント」に関する出題でした。アラインメントとは、メモリー上にデータを置くときに都合がいい「区切り」のことです。

例えばuint32_tのサイズは4バイトですね。多くのコンピューターでは、4バイトのデータは4の倍数のアドレスに配置されます。そうすることによって、データを高速で読み書きできるようになっているのです。

構造体のメンバーについても、話は同じです。今回の構造体は、こういう形をしていました。

typedef struct {
  uint8_t letter;
  uint32_t order;
} AlphabetInfo;

uint8_tのサイズは1バイトですね。その直後に4バイトのuint32_tを置くのは都合が悪いので、メモリー上ではletterのあとは3バイトとばしてorderが配置されます。ちなみに、このような3バイト分のメモリーは、間を埋めているだけなので「パディング」と呼ばれます。

そういうわけで、構造体のサイズは、すべてのメンバーのサイズを合計した値と一致するとは限りません。この現象は、次のようにすれば確かめられるでしょう。

サンプルコード
  printf("total size  = %zu\n", sizeof(uint8_t) + sizeof(uint32_t));
  printf("struct size = %zu\n", sizeof(AlphabetInfo));
実行結果
total size  = 5
struct size = 8
うわぁ、ほんとに3バイト増えてますよ!
でしょ。つまりね、構造体のサイズを知りたかったら、素直にsizeof(構造体名)とすればいいのよ。
そっか。そう言われると単純な答えですね。
じゃあ、構造体のメンバーの並び順を入れ替えたらどうなると思う?
えっ?
並び順を入れ替えるとuint8_t、つまり1バイトのメンバーがうしろにくるから……
typedef struct {
  uint32_t order;
  uint8_t letter;
} AlphabetInfo;
パディングは必要ないんじゃないかな。確かめてみよう!
  printf("total size  = %zu\n", sizeof(uint32_t) + sizeof(uint8_t));
  printf("struct size = %zu\n", sizeof(AlphabetInfo));
実行結果
total size  = 5
struct size = 8
ええっ!入れ替えてもサイズが変わらないじゃないですか!
そうなのよ。構造体の最後に、やっぱり3バイトのパディングが入っているわね。なんでか分かる?
うーん、メモリーを無駄遣いしているようにしか思えないんですけど……
これはね、配列にしたときのことまで考えられているからなの。
構造体の配列ってことですか?
そう。配列って、要素が隙間なく並べられるでしょう?だから、もし最後にパディングがなかったら……
あ、もしかして……。すぐ隣の要素のアラインメントがずれちゃうってことですか?
そういうこと!
なるほどー!うまく考えられてるんですね!
ここがポイント!
メモリー上のデータは、サイズによって都合のいい「区切り」で配置されていることを覚えておこう!

修正後のプログラム

main.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <ctype.h>

typedef struct {
  uint8_t letter;
  uint32_t order;
} AlphabetInfo;

AlphabetInfo *mallocAlphabetInfo(uint8_t letter) {
  const char *pAll = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
  const char *pFound = strchr(pAll, toupper(letter));
  if (pFound) {
    AlphabetInfo *info = malloc(sizeof(AlphabetInfo));
    if (info) {
      info->letter = letter;
      info->order = 1 + (uint32_t)(pFound - pAll);

      return info;
    }
  }

  return 0;
}

int main(void) {
  AlphabetInfo *info = mallocAlphabetInfo('x');
  if (info) {
    printf("'%c' is the %dth letter.\n", info->letter, info->order);

    free(info);
    info = 0;
  }

  return EXIT_SUCCESS;
}
実行結果
'x' is the 24th letter.