メールが来ました.学内の先生からです.アルドゥイーノのスケッチのプログラミングで,定数を定義するのに,ある本ではconst intを使っており,また別の本では#defineを使っているのだけれど,夏休みのプログラミング教室で教える際には,どちらのほうが良いのか,とのこと.……
const int ledPin = 13;
と
#define ledPin 13
の違い,というわけですが,一般論としては,const intで書く方が,“Cらしい”と言っていいでしょう.
ただし,もしこのledPinが,配列宣言時の要素数(int array[ledPin];)や,switch〜caseのラベル(case ledPin:)として出現する場合には,変数名だと都合が悪いので,#define を選ぶ必要があります.まあ,const intを使っている本があるということなので,こんな事例はないのでしょうね.
もう少し,調べてみました.Sketchのチュートリアル(http://arduino.cc/en/Tutorial/Sketch)には,「int ledPin = 13;」と書かれていました.その一方で,wikipedia:Arduinoおよびwikipedia:en:Arduinoでは,サンプルコードの中に「#define LED_PIN 13」とあります.Webにおいても,どちらでもよさそうです.
ArduinoやSketchから離れて,C言語として,定義の仕方を調べてみました.
まず用語として,「定数」はCの規格では特定の意味があるのでした.具体的には,JIS X 3010:2003では,定数は「整数定数」「浮動小数点定数」「列挙定数」「文字定数」のいずれかとしています.なので,上のコードのどちらであっても,ledPinを定数とみなすのは,規格から外れた解釈となります.
まあそれはそれとして,constか#defineかについては,英語の議論を見つけました.
この中で,Jonathan Leffler (answered Nov 4 '09 at 15:17)の意見が秀逸でした.
It depends on what you need the value for. You (and everyone else so far) omitted the third alternative:
- static const int var = 5;
- #define var 5
- enum { var = 5 };
Ignoring issues about the choice of name, then:
- If you need to pass a pointer around, you must use (1).
- Since (2) is apparently an option, you don't need to pass pointers around.
- Both (1) and (3) have a symbol in the debugger's symbol table - that makes debugging easier. It is more likely that (2) will not have a symbol, leaving you wondering what it is.
- (1) cannot be used as a dimension for arrays at global scope; both (2) and (3) can.
- (1) cannot be used as a dimension for static arrays at function scope; both (2) and (3) can.
- Under C99, all of these can be used for local arrays. Technically, using (1) would imply the use of a VLA (variable-length array), though the dimension referenced by 'var' would of course be fixed at size 5.
- (1) cannot be used in places like switch statements; both (2) and (3) can.
- (2) can change code that you didn't want changed because it is used by the preprocessor; both (1) and (3) will not have unexpected side-effects like that.
- You can detect whether (2) has been set in the preprocessor; neither (1) nor (3) allows that.
So, in most contexts, prefer the 'enum' over the alternatives. Otherwise, the first and last bullet points are likely to be the controlling factors - and you have to think harder if you need to satisfy both at once.
If you were asking about C++, then you'd use option (1) - the static const - every time.
訳してみます.
どのコードを選ぶかは,その値を何に使いたいかによる.3番目の選択肢があることを忘れているよ.
static const int var = 5; /* (1) */ #define var 5 /* (2) */ enum { var = 5 }; /* (3) */名前のつけ方は無視するとして,これらには次の違いがある.
- そこからポインタを得る*1のなら,(1)を使わないといけない.
- (2)は明らかに,そこからポインタを得ることができない.
- (1)と(3)はデバッガのシンボルテーブル内でシンボルが作られる.ということでデバッグ作業がより容易になる
デバッガに優しい.(2)ではシンボルが作られないので,デバッグ時に都合が悪い.- (1)はグローバルスコープの配列の次元として使うことができない.(2)と(3)ならできる.
- (1)は関数スコープの静的配列の次元として使うことができない.(2)と(3)ならできる.
- C99では,これらのいずれもローカルな配列(の次元)として使うことができる.技術的には,(1)を使うと可変長配列になるが,その次元はvarにより5に固定される.
- (1)はswitch文なんかの定数として使うことができない.(2)と(3)ならできる.
- (2)は,前処理の段階で望まないコードの変更ができてしまう.(1)と(3)ではそのような期待されない副作用は起こらない.
- (2)では前処理の段階で値の設定がなされることを検出できる.(1)と(3)ではできない.
なので,多くの状況で,enumを使った(3)は,(1)や(2)よりも良い.別の面では,箇条書きの最初と最後の項目に注意をしたい.同時に両方を満たしたいのなら,よく考えないといけない.
C++だと,常に,static constの(1)を使えばいい.
コードを書いて動作確認してみました.ソースファイルはGistに置いています.mainのほかに2つの関数を定義していて,test_arrayは,varを要素数とする配列の定義,test_switchではvarと定数式としたswitch〜caseを書いています.「static const int var = 5;」を使った方法は,gcc -DOPTION_1 constant.c -o constantによりコンパイルできますが,「error: case label does not reduce to an integer constant」とエラーが出ます*2.
「デバッグ作業がより容易になるデバッガに優しい」も,確かめたいところです.ソースファイルは共通で,コンパイルは以下の3通りの方法をとりました.gdb constantでデバッガを起動し,「b 24」「r」をしたあと,varとarrayの値を見たところ,次のようになりました.
(gdb) p var $1 = 5 (gdb) whatis var type = const int (gdb) p &var $2 = (const int *) 0x100403030 <var> (gdb) printf "%d\n", var 5 (gdb) p array $3 = 0x22aa10 (gdb) whatis array type = int []
(gdb) p var No symbol "var" in current context. (gdb) whatis var No symbol "var" in current context. (gdb) p &var No symbol "var" in current context. (gdb) printf "%d\n", var No symbol "var" in current context. (gdb) p array $1 = {0, 1, 2, 3, 4} (gdb) whatis array type = int [5]
(gdb) p var $1 = var (gdb) whatis var type = enum {...} (gdb) p &var Can't take address of "var" which isn't an lvalue. (gdb) printf "%d\n", var 5 (gdb) p array $2 = {0, 1, 2, 3, 4} (gdb) whatis array type = int [5]
2番目のコンパイル方法およびgdbで,varが式に使えないのは確かに不便です.またarrayの型が,1番目と2・3番目で異なることも確認できました.
とはいえ日常,ここまで違いを理解した上で,enumの定数(列挙定数)を定義して使うことはまあないかなとも思います.C言語の厄介なところをまたひとつ,目にする機会となりました.
(最終更新:2016-10-01 朝)