組み込みでこそc++を使う10の理由
TRANSCRIPT
組み込みでこそ
C++を使う100の理由
Aiming 大阪社内勉強会
2012-06-11 @kikairoya
喋ること
• C滅びろ
• C滅びろ
• C滅びろ
• C滅びろ
• C滅びろ
「Cを使う必然性」の幻想
• C++使うと遅くなるしCでいいよ
• …同じならCでよくね?
• いや例外とかでかいじゃん
• templateとかよくわかんないし
C++使うと遅くなるしCでいいよ
• そうでもない
• 同じことを実現しようとすれば、ほぼ正確に同じだけの命令数が必要
– Cで書いてもC++で書いても生成コードサイズは大して変わらない(RTTIと例外テーブルは除く)
• 20年前の昔なら兎も角、21世紀も10年過ぎた現在ではJavaですらCより速い場合がある
「C++遅い」の主要因
• 無意味な仮想関数
– dynamic polymorphismを使わないなら要らない
• newの乱発
– Cと同じように単にスタックに配置すればいい
• SjLj例外ハンドリング
– Dw2例外ハンドリングを使うか、OFFにする
おまけ:Javaのパフォーマンス
• http://blog.cfelde.com/2010/06/c-vs-java-
performance/ とか
g++ 4.3
vs
Sun Java HotSpot VM, version 1.6.0_20
…同じならCでよくね?
• んなこたない
• C++でしか出来ないことはあるけれど、Cでしか出来ないことはほとんど無い
– C99の複合リテラル・名前付き初期化くらい
– どちらもC++で似たような機能は実現可能
– C89に限定すれば事実上「全く」無いと言える
いや例外とかでかいじゃん
• そげなことない
• 不要ならOFFにすればいいだけ
• RTTIも同様
• 例外飛ばしてたらリアルタイム性守れない場合があるので、その場合はOFF
• 例外無くてもデストラクタは正しく走るので、リソース管理も安全安心
templateとかよくわかんないし
• 面倒な部分はライブラリ側に全部隠せます
• エラーコードは確かに量多いですが… 実行時に不思議な挙動をするか コンパイル時に説教されるか どっちがいいですか?
• コンパイル通した時点でデバッグ終了が理想
–現実は厳しいけれど、コンパイラが見つけてくれるエラーの量はCとは段違い
便利で危険なprintf
• printfって便利だけど危ないよね…
• sizeof(int)==2の環境だと特に問題が顕在化しやすい
• それC++なら型安全に出来るよ
• ntfmtとか
• cprintfとか
printfの罠
• たとえばこんなコード
extern int g_value_for_something;
void logger() {
printf("value:%d¥n",
g_value_for_something);
}
printfの罠
• 「intが16ビットですぐ溢れるからlongにしよう」
extern long g_value_for_something;
void logger() {
printf("value:%d¥n",
g_value_for_something);
}
printfの罠
• 「intが16ビットですぐ溢れるからlongにしよう」
extern long g_value_for_something;
void logger() {
printf("value:%d¥n",
g_value_for_something);
}
!!!フォーマット指定子を変え忘れた!!!
constexprなprintfなら
• 変え忘れたらコンパイルエラーにできます
extern long g_value_for_something;
void logger() {
cprintf("value:%d¥n",
g_value_for_something);
} FORMAT_ERROR_format_specifier_d_takes_int_but_
given<T>::fail() [with T = long int]
void *パレード
• Cで汎用コンテナやアルゴリズム作ろうとすると、void *の山になりますよね…
• ライブラリで閉じていればまだいいけど、ユーザコードでvoid *からキャストする必要がある
• それC++なら型安全に出来るよ
• STLとか
ビットフィールドの恐怖
• 組み込みでも一番低いレイヤではハードウェアレジスタを叩く必要がある
• Cだとよくビットフィールド使うけれど…
• GCCでは間違ったアドレスにアクセスすることがある!
ビットフィールドの恐怖
• こんなありふれたコードから…
volatile struct {
volatile unsigned int a: 8;
volatile unsigned int b: 4;
} bitfield;
int main() {
bitfield.a = 1;
}
ビットフィールドの恐怖
• こんな恐ろしいコードが!!!
movl $1, bitfield+3(%rip)
↑ (bitfield+3)番地に32ビットで書き込み
ビットフィールドの恐怖
• 組み込みでも一番低いレイヤではハードウェアレジスタを叩く必要がある
• Cだとよくビットフィールド使うけれど…
• GCCでは間違ったアドレスにアクセスすることがある!(注: BTSにパッチ投稿済)
–ベンダコンパイラでもコンパイルオプション一つでビットオーダーが変わることがある
• それC++なら安全でポータブルに出来るよ
ビットフィールドの克服
• こんなコードを用意しておけば…
struct addr_t { size_t addr; };
template <typename T, int H, int L = H>
struct ioaccess_bit {
template <int = bitsizeof(H)-1, int = H>
struct gen_mask {
static constexpr T value = (~static_cast<T>(0)<<H+1) ^ (~static_cast<T>(0)<<L);
};
template <int h>
struct gen_mask<h, h> { static constexpr T value = ~(~static_cast<T>(0) << L); };
constexpr ioaccess_bit(addr_t addr): addr(addr.addr) { }
size_t addr;
// continue
ビットフィールドの克服
• こんなコードを用意しておけば…
// continued
size_t addr;
static constexpr T mask = gen_mask<>::value;
constexpr T omit_bits(T x) { return x & ~mask; }
constexpr T extract_bits(T x) { return (x & mask) >> L; }
constexpr T shift_bits(T x) { return (x << L) & mask; }
volatile T *get_addr() const { return reinterpret_cast<volatile T *>(addr); }
operator T() const { return extract_bits(*get_addr()); }
ioaccess_bit operator =(T v) const {
T tmp = *get_addr(); *get_addr() = omit_bits(tmp) | shift_bits(v); return *this;
}
};
ビットフィールドの克服
• こんな定義で… struct { struct reg_t { struct can_mb_id_t {
struct bit_t {
static constexpr size_t base = 0x00090200;
size_t offset;
ioaccess_bit<uint32_t, 31> IDE = addr_t{base+offset};
ioaccess_bit<uint32_t, 30> RTR = addr_t{base+offset};
ioaccess_bit<uint32_t, 28, 18> SID = addr_t{base+offset};
ioaccess_bit<uint32_t, 17, 0> EID = addr_t{base+offset};
bit_t(size_t off): offset(off) { }
} BIT;
} ID; };
reg_t operator[] (size_t n) { return reg_t{{{n}}}; }
} MB;
ビットフィールドの克服
• こんなコードが書けます。
int n = MB[0].ID.BIT.SID;
MB[0].ID.BIT.EID = 12;
assert(MB[1].ID.BIT.IDE == 1);
割り込みマスク
• 割り込みを一時的にマスクしたいこと、有りますよね
• sti()/cli()でいいんだけど、もしネストされてたら…?
• というかcli()とか呼ぶの忘れますよね
• それC++なら自動化できるよ
単純な例
struct lock_interrupt {
lock_interrupt() : masked(get_imask_ccr()) {
set_imask_ccr(1);
}
~lock_interrupt() {
set_imask_ccr(masked);
}
bool masked;
};
// in some function...
lock_interrupt lk;
// do critical action, let's forget unmask interrupt flag!
}
醜い固定小数点演算
• Cで実数演算する場合は…
– double/floatを使う
–整数を自前でシフトして使う
–固定小数点演算ライブラリを作る
• 固定小数点を使う場合、演算子使えなくて大変ですよね…
• それC++ならスマートに出来るよ
美しい固定小数点演算
fixed calc_1(
fixed v,
fixed u
) {
v *= 1.5f;
return v + calc_2(u);
}
void calc_1(
fixed *result,
fixed *pv,
fixed *pu
) {
fixed tmp;
fixed_from_float(&tmp, 1.5f);
fixed_mul(pv, pu, &tmp);
calc_2(&tmp, pu);
fixed_plus(result, pv, &tmp);
}
美しい固定小数点演算
• 演算子オーバーロードは用法・容量を守って正しく使いましょう。
まずは簡単なところから
• たとえC++の機能を使っていなくても、拡張子は.cpp
にしましょう
• マクロで定数やインライン関数を書くのはやめて、static constやinlineを使いましょう
• テンプレートを「書く」のは慣れてからで十分
• 千里の道も一歩から
C++マスターもBetter Cから
おしまい
• Let's C++!!!