KiRi-LOG

貧乏サラリーマンが現代社会を生き抜くために、節約とか副業とか投資とか色々やっていく有様を記録していくブログ。

技術メモ

【C言語】定数配列について / やってはいけない例と正しい宣言方法

【C言語】定数配列について(やってはいけない例と正しい方法)

 

どうも、キリログ(@KiRi_LOG_BRZ)です。

 

今回はC言語の定数配列の宣言について、なかなか恥ずかしいことをやらかしていた経験を戒めのために覚書として記事にします。

アホ過ぎる過ち / C言語・定数配列の宣言方法

昔自分が作った組み込みのプログラムを見返す機会があり、読んでみるとなかなかやらかしていた。

/* hoge.h */
static const ARRAY[] = { 0, 1, 2, 3, 4 };

/* hoge.c */
#include "hoge.h"

void hoge(void)
{
  // 省略
}

/* main.c */
#include "hoge.h"

void main(void)
{
  // 省略
}

 

 

間違いを認識する / C言語・定数配列の宣言方法

取り敢えず、間違っているとは言え、上記コードがやりたいのはグローバルな定数配列を宣言し、各ソースファイルで共有したい。というもの。

 

間違い①「static」の意味がわかってない。

本来、staticを使用すると指定された変数や関数のスコープが宣言されたソースファイル内に限定される。

 

例えば、下記のように宣言した変数「value」がhoge.c内ではtasuとhikuの両方で使えるグローバル変数となるが、main.cのでは使えない。

/* hoge.c */
static unsigned char value = 0;

unsigned char tatu(void)
{
  return (++value);
}

unsigned char hiku(void)
{
  return (--value);
}

/* main.c */
void main(void)
{
  unsigned char i = value; /* エラー(value?そんなもん知らんわボケ!) */
}

 

間違い②ヘッダファイルにグローバル変数を宣言。

ヘッダファイルに変数を宣言するということは、このヘッダをインクルードしたソースコードにそれぞれの変数の実体が作成されるということ。

 

下記のようにhoge.cとmain.cで使用しているvalueは全くの別々の変数として各ソースコードに実体が作成される。よって片方で値が変わろうが何されようが、もう片方では知ったことではない。

/* hoge.h */
static unsigned char value = 1;

/* hoge.c */
#include "hoge.h"

void hoge(void)
{
  value++; // 1 + 1 = 2
}

/* main.c */
#include "hoge.h"

void main(void)
{
  value *= 3; /* 1 * 3 = 3 */
}

 

 

正しいやり方 / C言語・定数配列の宣言方法

外部に公開したいグローバル変数にはextern宣言を使用する。下記のようにhoge.cに宣言した「value」をhoge.hにextern宣言で追記する。

 

こうすることで、hoge.hをインクルードしたソースファイルには「valueというグローバル変数が他の場所で宣言されていて使えるよ。」という認識になり、hoge.cとhogehoge.cの「value」は同じ変数という認識になる。

/* hoge.h */
extern unsigned char value; /* 実体はhoge.c */
extern void hoge(void);

/* hoge.c */
unsingned char value = 5; /* 実体はここ。 */

void hoge(void)
{
  value *= 2;
}

/* main.c */
#include "hoge.h"

void main(void)
{
  hoge(); /* 5 * 2= 10 */
  value++; /* 10 + 1 = 11 */
}

 

本題に戻り『定数配列』を宣言する場合でも同じです。

 

例えば下記のようにhoge.cに「ARRAY」というconst配列を宣言し、hoge.hにextern宣言で追加する。

 

こうすることで、hoge.hをインクルードしたソースファイルにも「ARRAY」という定数配列が使用でき、かつ実体はhoge.cに一つだけなので、無駄な容量も食わない。

/* hoge.h */
extern unsigned char ARRAY[]; /* 実体はhoge.c */

/* hoge.c */
const unsigned char ARRAY[] = {0, 1, 2, 3};

/* main.c */
#include "hoge.h"

void main(void)
{
  unsigned char BUFF[] = {4, 5, 6, 7};

  if(memcmp(ARRAY, BUFF, 4) == 0)
  {
    /* 中身は同じ */
  }
  else
  {
   /*中身は違う */
  }
}

 

 

配列数を取得する場合の注意 / C言語・定数配列の宣言方法

配列を宣言する際、配列数を省略できるため配列内部の初期値が決まっている定数配列の場合は、配列数を書かないことが多い。

 

extern宣言時にも省略は可能だが、この場合extern宣言で変数を認識してるソースファイルからは、sizeofで配列数を読み取ろうとするとエラーが発生する。

/* hoge.c */
const ARRAY[] = {0, 1, 2, 3};

/* hoge.h */
extern const ARRAY[];

/* main.c */
#include "hoge.h"

void main(void)
{
  unsigned char len = 0;
  len = sizeof(ARRAY); // コンパイルエラー
}

 

解決手段としては、単純にextern宣言時も配列数を記入すればいいだけです。

 

実体のあるソースファイル側は省略できますが、バラバラに書くとややこしくなるため、省略せずに両方に配列数を記入することをオススメします。

/* hoge.c */
const ARRAY[4] = {0, 1, 2, 3};

/* hoge.h */
extern const ARRAY[4];

 

もしくはヘッダーファイルに配列数をマクロで定義してしまうのもあり。

/* hoge.h */
#define MAX_ARRAY_LEN (4)

extern const ARRAY[MAX_ARRAY_LEN];

/* hoge.c */
#include "hoge.h"

const ARRAY[MAX_ARRAY_LEN] = {0, 1, 2, 3};

/* hoge.c */
#include "hoge.h"

void main(void)
{
  /* ARRAY & MAX_ARRAY_LEN使用可能 */
}

 

 

反省 / C言語・定数配列の宣言方法

 

今でも本当に恥ずかしい・・・。

 

マイコンの容量が圧迫されるほどの大きい実装内容ではなかったのが唯一の救い。

 

プログラミングの勉強を始めたばかりの初心者の人や独学で勉強している人は、参考書とにらめっこしているだけなく実際にコードを書いて、書いて、書いて、書いて、書いてとことん書いて勉強して下さい。それが一番の勉強方法です。

 

以上、有難うございましたm(_ _)m

 

現役プログラマーがオススメする良書。

  • この記事を書いた人

KiRi

現役貧乏サラリーマン。仮想通貨をきっかけに投資に興味を持ち、現在では「iDeCo」や「つみたてNISA」など堅実な投資に勤しむ。貪欲に図太くしぶとく生きることが目標。

-技術メモ
-

Copyright© KiRi-LOG , 2019 All Rights Reserved.