2015年1月15日木曜日

GCCのコンパイラ警告に依存したコードの書き方について

ちょっと気になることが有ったのでまとめ
(諸事情で回線速度が80kbpsくらいしか出てないのでネットの情報を漁ったりはしてません 詳しく書いてるサイトがあったら教えてください)

1. 普通に書く

まずこういうコードを用意します

#include <stdio.h>

typedef enum { hoge1_1, hoge1_2, hoge1_3, hoge1_4 } hoge1_t;
typedef enum { hoge2_1, hoge2_2, hoge2_3, hoge2_4 } hoge2_t;

typedef struct {
    hoge1_t hoge1;
    hoge2_t hoge2;
} type_t;

int main(void) {

    type_t type;
    type.hoge1 = hoge1_1;
    type.hoge2 = hoge2_2;

    printf("type size:%d\n", (int)(sizeof(type_t)));

    printf("type.hoge1: ");
    switch (type.hoge1) {
    case hoge1_1: printf("hoge1\n"); break;
    case hoge1_2: printf("hoge2\n"); break;
    case hoge1_3: printf("hoge3\n"); break;
    case hoge1_4: printf("hoge4\n"); break;
    }
    
    printf("type.hoge2: ");
    switch (type.hoge2) {
    case hoge2_1: printf("hoge1\n"); break;
    case hoge2_2: printf("hoge2\n"); break;
    case hoge2_3: printf("hoge3\n"); break;
    case hoge2_4: printf("hoge4\n"); break;
    }

    return(0);
}

見るからにCのコードですが、g++でビルドします
$ g++ test.c -Wall
という感じで
もちろん警告は出ません
実行すると
$ ./a.exe
type size:8
type.hoge1: hoge1
type.hoge2: hoge2
という感じになります

2. ちょっと端折った書き方をしてみる

列挙型の一部は使用していません switchで使用していない分岐を持つのはコード効率が悪いのでコメントアウトしてみましょう

#include <stdio.h>

typedef enum { hoge1_1, hoge1_2, hoge1_3, hoge1_4 } hoge1_t;
typedef enum { hoge2_1, hoge2_2, hoge2_3, hoge2_4 } hoge2_t;

typedef struct {
    hoge1_t hoge1;
    hoge2_t hoge2;
} type_t;

int main(void) {

    type_t type;
    type.hoge1 = hoge1_1;
    type.hoge2 = hoge2_2;

    printf("type size:%d\n", (int)(sizeof(type_t)));

    printf("type.hoge1: ");
    switch (type.hoge1) {
    case hoge1_1: printf("hoge1\n"); break;
    case hoge1_2: printf("hoge2\n"); break;
    case hoge1_3: printf("hoge3\n"); break;
    //case hoge1_4: printf("hoge4\n"); break;
    }
    
    printf("type.hoge2: ");
    switch (type.hoge2) {
    case hoge2_1: printf("hoge1\n"); break;
    case hoge2_2: printf("hoge2\n"); break;
    case hoge2_3: printf("hoge3\n"); break;
    //case hoge2_4: printf("hoge4\n"); break;
    }

    return(0);
}

これを1.と同じようにビルドしてみましょう
$ g++ main.c -Wall
main.c: 関数 ‘int main()’ 内:
main.c:20:9: 警告: 列挙値 ‘hoge1_4’ は switch 内で取り扱われません [-Wswitch]
  switch (type.hoge1) {
         ^
main.c:28:9: 警告: 列挙値 ‘hoge2_4’ は switch 内で取り扱われません [-Wswitch]
  switch (type.hoge2) {
         ^
警告がでてしまいました
これは便利ですね 列挙型の書き忘れを防ぐことができます

3. 消費メモリをカリカリ削る

しかし実行してみるとtype_t型は8バイトのメモリを使用しています
それぞれの列挙型は4種類しか値を持たないのでそれぞれ2bitあれば事足りるはずです
ということでビットフィールドで2bitを指定しましょう

#include <stdio.h>

typedef enum { hoge1_1, hoge1_2, hoge1_3, hoge1_4 } hoge1_t;
typedef enum { hoge2_1, hoge2_2, hoge2_3, hoge2_4 } hoge2_t;

typedef struct {
    hoge1_t hoge1 : 2;
    hoge2_t hoge2 : 2;
} type_t;

int main(void) {

    type_t type;
    type.hoge1 = hoge1_1;
    type.hoge2 = hoge2_2;

    printf("type size:%d\n", (int)(sizeof(type_t)));

    printf("type.hoge1: ");
    switch (type.hoge1) {
    case hoge1_1: printf("hoge1\n"); break;
    case hoge1_2: printf("hoge2\n"); break;
    case hoge1_3: printf("hoge3\n"); break;
    case hoge1_4: printf("hoge4\n"); break;
    }
    
    printf("type.hoge2: ");
    switch (type.hoge2) {
    case hoge2_1: printf("hoge1\n"); break;
    case hoge2_2: printf("hoge2\n"); break;
    case hoge2_3: printf("hoge3\n"); break;
    case hoge2_4: printf("hoge4\n"); break;
    }

    return(0);
}

同じようにビルドしてみましょう もちろん警告はでません
では実行してみましょう

$ ./a.exe
type size:4
type.hoge1: hoge1
type.hoge2: hoge2

消費メモリは4バイトに減っています
メモリ効率が倍になりました めでたしめでたし

4. 消費メモリをカリカリ削る + 端折った書き方

では、使っていない列挙のcaseをコメントアウトしてみましょう

#include <stdio.h>

typedef enum { hoge1_1, hoge1_2, hoge1_3, hoge1_4 } hoge1_t;
typedef enum { hoge2_1, hoge2_2, hoge2_3, hoge2_4 } hoge2_t;

typedef struct {
    hoge1_t hoge1 : 2;
    hoge2_t hoge2 : 2;
} type_t;

int main(void) {

    type_t type;
    type.hoge1 = hoge1_1;
    type.hoge2 = hoge2_2;

    printf("type size:%d\n", (int)(sizeof(type_t)));

    printf("type.hoge1: ");
    switch (type.hoge1) {
    case hoge1_1: printf("hoge1\n"); break;
    case hoge1_2: printf("hoge2\n"); break;
    case hoge1_3: printf("hoge3\n"); break;
    //case hoge1_4: printf("hoge4\n"); break;
    }
    
    printf("type.hoge2: ");
    switch (type.hoge2) {
    case hoge2_1: printf("hoge1\n"); break;
    case hoge2_2: printf("hoge2\n"); break;
    case hoge2_3: printf("hoge3\n"); break;
    //case hoge2_4: printf("hoge4\n"); break;
    }

    return(0);
}

このようになります
ビルドしてみると
$ g++ main.c -Wall
警告がでません
これはこまった…

メモリ効率を優先すると未使用のcaseに対する警告がでなくなってしまいました

組み込みの場合は消費メモリは減らしたいし、case分岐忘れは致命的だし という場合がありますが、どうしたらいいのでしょう…

というのが今日の本題
本題というかもうこれしかネタが無いのですが、こういう時はどうすればいいんでしょうかねぇ…


ちなみに


#include <stdio.h>
#include <stdint.h>

typedef enum { hoge1_1, hoge1_2, hoge1_3, hoge1_4 } hoge1_t;
typedef enum { hoge2_1, hoge2_2, hoge2_3, hoge2_4 } hoge2_t;

typedef struct {
    uint8_t hoge1 : 2;
    uint8_t hoge2 : 2;
} type_t;

int main(void) {

    type_t type;
    type.hoge1 = hoge1_1;
    type.hoge2 = hoge2_2;

    printf("type size:%d\n", (int)(sizeof(type_t)));

    printf("type.hoge1: ");
    switch (type.hoge1) {
    case hoge1_1: printf("hoge1\n"); break;
    case hoge1_2: printf("hoge2\n"); break;
    case hoge1_3: printf("hoge3\n"); break;
    case hoge1_4: printf("hoge4\n"); break;
    }
    
    printf("type.hoge2: ");
    switch (type.hoge2) {
    case hoge2_1: printf("hoge1\n"); break;
    case hoge2_2: printf("hoge2\n"); break;
    case hoge2_3: printf("hoge3\n"); break;
    case hoge2_4: printf("hoge4\n"); break;
    }

    return(0);
}

というコードにすると、実行結果は
$ ./a.exe
type size:1
type.hoge1: hoge1
type.hoge2: hoge2
となります
メモリ消費量が1バイトになりました(もちろんビットフィールドを使っているのでcase忘れ警告はでませんが)

列挙型では4バイトを使ってしまうのは、暗黙的にint(4バイト)を使用し、そのビットを区切って使っているからだと思われます


メモリがカツカツな環境で構造体を使いたい場合はどうすればいいんですかねぇ
enumは#defineと同じような感覚で使って、uint8_tのビットフィールドで使用メモリを減らす case忘れはコーダーが気をつける という感じになるんでしょうか

そもそもコンフィグレジスタの値をtypedefで持とうとしてる時点でメモリ消費量なんて… という感じではありますが

僕の知る限りどのルートを通ってもあっちを立てればこっちが立たずです
なにかいい方法があったら教えてください

0 件のコメント:

コメントを投稿