「C/C++ 関数・マクロ集」というタイトルですが, そのうちのいくつかはC専用だったりします.(苦笑)
2007/06/24(日) 追記高木さんより,
Cの規格上移植性に問題がある点をご指摘いただいたので,
現在修正中です.
(たくさんあります….orz)
とはいってもその多くは,
めったにお目にかかれないような珍しい処理系とか,
「そんなの実在するの?」という処理系に移植する場合の話なので,
実用上ほとんどの場合は問題ないと思います.
(一部そうとはいえないものもありますが.)
Cの規格に照らして完全に「処理系・OS 非依存」 にするのは困難な場合もあり, 完璧な移植性にこだわるあまりプログラムが書けなくなっては本末転倒なので, タイトルに「ほぼ?」を入れました.orz
2007/06/21(木) 追記このページを含め,私が C/C++
関連記事を書くに当たりたびたび参考に&リンクさせていただいている
「株式会社きじねこ」の高木さんが,
このページのマクロをパロディ
C++ テンプレート化したものを公開なさっています.
C++ ユーザの方はぜひご覧ください.
「C/C++ 関数・マクロ集」と書いておきながら,実は C++ はたまにしか使っていないので,C++ 専用のコンテンツはなかなか増えそうにありません (看板に偽りあり). そういうわけで,このページの C++ 化は高木さんにお願いしたいと思います (他力本願).
このページの主な更新は Blog でお知らせします.
次のマクロは,整数型 intType が符号付のときそのときに限り真を返す.
// 2007/06/24(日) 移植性の問題を修正. #define IntegerIsSigned(intType) ((intType)(-1) < 0)
// 旧版:移植性に問題あり. // 1の補数を使用する処理系では,全ビットが1の場合,それが // 負の0かトラップ表現かは処理系定義.また,~ の演算結果が // トラップ表現を生成する場合の動作は未定義.#define IntegerIsSigned(intType) ((intType)~(intType)0 < 0)
使用例:
printf("size_t は符号%s\n", IntegerIsSigned(size_t) ? "付" : "無"); printf("ptrdiff_t は符号%s\n", IntegerIsSigned(ptrdiff_t) ? "付" : "無");
2006/09/03(日) 考案 |
2007/06/24(日) 移植性の問題を修正 |
次のマクロは,整数型 intType が符号付で, 負数を2の補数で表現するときそのときに限り真を返す.
#define IntegerIsTwosComplement(intType) \ (IntegerIsSigned(intType) && \ ((intType)((((intType)1 << (BitSizeOf(intType) - 2)) - 1) << 2) \ == (intType)(-4)))
上記の == の左辺は,下位2ビットのみが0となるビットパターン.
&& 以後の部分を次のようにすると,移植性に問題あり.
●((intType)~(intType)0 == (intType)(-1)) 左辺が IntegerIsSigned の旧版と同じ問題を引き起こす可能性がある. ●((intType)(~((intType)1 << (BitSizeOf(intType) - 1)) << 1) == (intType)(-2)) この途中結果 (intType)1 << (BitSizeOf(intType) - 1) は MSB のみが1と なるビットパターン.2の補数の場合,これが有効な値かトラップ表現かは 処理系定義.■注意
2007/06/25(月) 考案 |
次のマクロは,整数型 intType が符号付で, 負数を1の補数で表現するときそのときに限り真を返す.
#define IntegerIsOnesComplement(intType) \ (IntegerIsSigned(intType) && \ ((intType)((((intType)1 << (BitSizeOf(intType) - 2)) - 1) << 2) \ == (intType)(-3)))
IntegerIsTwosComplement() の -4 を -3 に変えただけ.
■注意
IntegerIsTwosComplement() の注意書きと同文.
2007/06/25(月) 考案 |
■参考 (2016/06/04(土) 追記)
1の補数表現を使用するコンピュータとしては次のものがあったらしい.
(出典:Signed number representations (Wikipedia 英語版))
次のマクロは,整数型 intType が符号付で, 負数を「符号ビット+絶対値」で表現するときそのときに限り真を返す.
#define IntegerIsSignAndAbs(intType) \ (IntegerIsSigned(intType) && \ ((intType)((((intType)1 << (BitSizeOf(intType) - 2)) + 1) << 1) \ == (intType)(-2)))
== の左辺は,ビットパターン 10……010.
■注意IntegerIsTwosComplement() の注意書きと同文.
2007/10/11(木) 追記初めて "8ビット=1バイト" の概念を導入したマシンである IBM 7030 Stretch も「符号+絶対値」形式だったらしい.(ただし整数じゃなくて固定小数点と書いてある.)
2007/06/25(月) 考案 |
特殊な処理系への移植性に問題あり.(修正方法検討中)
次の2つのマクロはそれぞれ,指定された整数型 intType が表現可能な最小値および最大値を返す.
#define IntegerMin(intType) \ (IntegerIsSigned(intType) \ ? (intType)((intType)1 << (BitSizeOf(intType) - 1)) : (intType)0) #define IntegerMax(intType) \ (IntegerIsSigned(intType) \ ? (intType)~((intType)1 << (BitSizeOf(intType) - 1)) : (intType)~(intType)0)
これらのマクロは, 負の整数を2の補数で表現する CPU 専用です. (そうでない CPU ってあるの?)
なお BitSizeOf() は,型のサイズをビット数で取得するマクロです (後述).
注意:これらのマクロは,intType の実際の有効ビット数が sizeof(intType) * CHAR_BIT と異なる場合には,正しい結果を返しません. (BitSizeOf() の注意書きを参照.)
使用例:printf("long 型の値の範囲は %ld 〜 %ld.\n", IntegerMin(long), IntegerMax(long)); printf("unsigned long 型の値の範囲は %lu 〜 %lu.\n", IntegerMin(unsigned long), IntegerMax(unsigned long));
/*───────────────────────────────────── 機能 :整数除算で端数を偶数丸めする.つまり, ・有理数 dividend/divisor を最も近い整数に丸める. ・ただし dividend/divisor の小数部がちょうど 0.5 の場合は, 最も近い偶数に丸める. ●別名 ・最近接偶数への丸め (round to the nearest even) ・最近接丸め ・JIS丸め (JIS Z 8401 規則A) ・ISO丸め (ISO 80000-1:2009 Annex B) ・銀行家の丸め (banker's rounding) ・五捨五入 ・偶捨奇入 入力 :(1) dividend (≧0):被除数. (2) divisor:除数.オーバーフローを避けるため, 0 < divisor ≦ (UINT_MAX - 1) / 2 + 1 でなければならない. 戻り値:有理数 dividend/divisor の小数部を偶数丸めした値. 規格 :(1) ISO 80000-1:2009 Quantities and units - Part 1: General Annex B Rounding of numbers (2) JIS Z 8401 規則A 2015/01/01(木) 考案・作成 ─────────────────────────────────────*/ #include <limits.h> #include <assert.h> unsigned RoundHalfToEven(unsigned dividend, unsigned divisor) { unsigned quot, rem, rem2; // 引数チェック assert(divisor > 0); assert(divisor <= (UINT_MAX - 1) / 2 + 1); // オーバーフロー防止 #if 賢い処理系 // 次の商と剰余を1回の除算命令 (またはサブルーチン呼び出し) で求める // コードを生成する賢い処理系ならば,こちらを使用する方が速い. // (VC2008/2012/2013 /O2 では最適化された.) quot = dividend / divisor; rem = dividend % divisor; #else // あまり賢くない処理系 // 上の商と剰余を求めるために (遅い) 除算を2回行うコードを生成する // あまり賢くない処理系ならば,こちらを使用する方が速いはず. // (乗算も遅いが,除算よりは速いので.) quot = dividend / divisor; rem = dividend - quot * divisor; #endif rem2 = rem * 2; assert(rem2 >= rem); // オーバーフロー検出 if(rem2 > divisor) { // 端数>0.5 の場合:切り上げる. quot++; } else if(rem2 == divisor) { // 端数=0.5 の場合:quot が奇数ならば偶数に切り上げる. #if 1 // 高速版 (整数加算命令 + ビット演算命令) quot++; quot &= ~1U; // quot を偶数に切り下げる. #else // やや低速版 // 上のコードと結果は同じだが,条件分岐命令を使う // コードにコンパイルされてしまうとちょっと遅い. if(quot & 1U) quot++; #endif } else { // 端数<0.5 の場合:切り捨てる (ここでは何もする必要なし). } return quot; }
■参考
/*───────────────────────────────────── 機能 :整数除算で端数を四捨五入する.(JIS Z 8401 規則B) 入力 :(1) dividend (≧0):被除数 (整数型). (2) divisor (>0):除数 (整数型). 戻り値:有理数 dividend/divisor の小数部を四捨五入した値.数学的には, RoundHalfUp(dividend, divisor) ≡ floor(dividend/divisor + 1/2). 2010/07/18(日) 考案・作成 2011/06/29(水) 改良版 (オーバーフローしにくいようにした.) 2015/01/03(土) 公開 ─────────────────────────────────────*/ #define RoundHalfUp(dividend, divisor) \ (((dividend) + (divisor) / 2) / (divisor))
■同様のマクロ (有名どころ)
■参考
/*───────────────────────────────────── 機能 :整数除算で端数切り上げ. Cの標準ライブラリには,浮動小数の小数部分を切り上げる関数 ceil() があるが,このマクロは ceil(dividend/divisor) の整数版. 整数同士の除算 dividend / divisor (dividend≧0,divisor>0) において, 小数部を切り上げた商を返す. 入力 :(1) dividend (≧0,整数型):被除数. (2) divisor (>0,整数型):除数. 戻り値:商 dividend/divisor の小数部分を切り上げた整数値. 注意 :オーバーフローを防ぐため,ICEIL(dividend, divisor) を計算する前に 次の条件が成立していなければならない.(xxx_MAX は dividend と divisor の型が int ならば INT_MAX,unsigned ならば UINT_MAX など.) #include <assert.h> #include <limits.h> assert(divisor - 1 <= xxx_MAX - dividend); (数学的には,(ICEIL の計算式の分子)≦xxx_MAX であることを確認している のだが,この式を次のようにそのままCで書いてしまうと左辺がオーバー フローする可能性があるのでまずい.) assert(dividend + divisor - 1 <= xxx_MAX); // NG 次の判定方法は,dividend と divisor が符号付整数型の場合は不可. 実際にオーバーフローしたか否かを判定しようと意図しているが, 符号付整数型ではオーバーフロー時の動作は未定義. (参考:Cのオーバーフローについて) assert(dividend + divisor - 1 >= dividend); // 符号付整数の場合はNG 証明 :dividend を divisor で割った商を q,余りを r (0≦r<divisor) とすると, dividend == divisor * q + r なので, ICEIL(dividend, divisor) == (dividend + divisor - 1) / divisor == (divisor * q + (divisor + r - 1)) / divisor == q + (divisor + r - 1) / divisor この第2項 (divisor + r - 1) / divisor に着目すると, (1) dividend/divisor が割り切れる場合は r==0 なので, 第2項 == (divisor - 1) / divisor この整数除算 (端数切り捨て) は分母より分子が小さいので0となる. したがって ICEIL(dividend, divisor) == q. (2) dividend/divisor が割り切れない場合は 1≦r≦divisor-1 なので, 第2項 == (divisor + r - 1) / divisor において divisor ≦ 分子 ≦ (2 * divisor - 2) < 2 * divisor であり,これを divisor で整数除算 (端数切り捨て) すると1になる. したがって ICEIL(dividend, divisor) == q + 1. つまり dividend/divisor の端数が切り上げられたことになる. 1990/03/11(日)以前? 考案 2015/02/21(土) 証明とオーバーフローに関する注意を追記. ─────────────────────────────────────*/ #define ICEIL(dividend, divisor) \ (((dividend) + ((divisor) - 1)) / (divisor))
■同様のマクロ (有名どころ)
「切り上げ C言語」,「切り上げ C++」 などで検索してくる人が不思議に多いので追加.
/*───────────────────────────────────── 機能 :整数除算で端数切り上げ (商を+∞方向に丸める). 書式 :div_t iceil(int dividend, int divisor); ldiv_t lceil(long dividend, long divisor); lldiv_t llceil(long long dividend, long long divisor); 入力 :(1) dividend:被除数 (任意の整数). (2) divisor:除数 (>0). 戻り値:商および剰余. (1) 商 (xdiv_t::quot):有理数 dividend/divisor の小数部を+∞方向に 丸めた整数値.つまり dividend/divisor 以上で最小の整数. (2) 剰余 (xdiv_t::rem) ・rem == dividend - quot * divisor ・-divisor < rem ≦ 0 参考 :端数処理 (Wikipedia) 1990/03/11(日) 作成 (初版) 2007/07/14(土) 作成 (Web 公開版) ─────────────────────────────────────*/ #include <assert.h> #include <stdlib.h> // 切り上げ関数を定義するマクロ. #define IMPLEMENT_xceil(xceil, intType, xdiv) \ xdiv##_t xceil(intType dividend, intType divisor) \ { \ xdiv##_t qr; \ \ assert(divisor > 0); \ qr = xdiv(dividend, divisor); \ if(qr.rem > 0) { \ qr.rem -= divisor; \ qr.quot++; \ } \ return qr; \ } // iceil() を定義する. IMPLEMENT_xceil(iceil, int, div) // lceil() を定義する. IMPLEMENT_xceil(lceil, long, ldiv) #if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) // C99 以後 // llceil() を定義する. IMPLEMENT_xceil(llceil, long long, lldiv) #endif /* defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) */
剰余付の切り下げは,「日付 ⇔ 通算日数変換」でも使用する予定なので追加.
/*───────────────────────────────────── 機能 :整数除算で端数切り下げ (商を−∞方向に丸める). 書式 :div_t ifloor(int dividend, int divisor); ldiv_t lfloor(long dividend, long divisor); lldiv_t llfloor(long long dividend, long long divisor); 入力 :(1) dividend:被除数 (任意の整数). (2) divisor:除数 (>0). 戻り値:商および剰余. (1) 商 (xdiv_t::quot):有理数 dividend/divisor の小数部を−∞方向に 丸めた整数値.つまり dividend/divisor 以下で最大の整数. (2) 剰余 (xdiv_t::rem) ・rem == dividend - quot * divisor ・0 ≦ rem < divisor 参考 :端数処理 (Wikipedia) 1990/03/11(日) 作成 (初版) 2007/07/14(土) 作成 (Web 公開版) ─────────────────────────────────────*/ #include <assert.h> #include <stdlib.h> // 切り下げ関数を定義するマクロ. #define IMPLEMENT_xfloor(xfloor, intType, xdiv) \ xdiv##_t xfloor(intType dividend, intType divisor) \ { \ xdiv##_t qr; \ \ assert(divisor > 0); \ qr = xdiv(dividend, divisor); \ if(qr.rem < 0) { \ qr.rem += divisor; \ qr.quot--; \ } \ return qr; \ } // ifloor() を定義する. IMPLEMENT_xfloor(ifloor, int, div) // lfloor() を定義する. IMPLEMENT_xfloor(lfloor, long, ldiv) #if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) // C99 以後 // llfloor() を定義する. IMPLEMENT_xfloor(llfloor, long long, lldiv) #endif /* defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) */
/*───────────────────────────────────── 機能 :C標準ライブラリ関数 div() の64ビット版. (C99 未対応で,div() の64ビット版がサポートされていない処理系用. ただし64ビット整数型はサポートされている必要がある.) 入力 :(1) dividend:被除数 (任意の整数). (2) divisor:除数 (≠0). 戻り値:商および剰余. (1) 商 (div64_t::quot):有理数 dividend/divisor の小数部を0方向に 丸めた整数値.つまり, ・絶対値:|dividend/divisor| 以下で最大の整数. ・符号:dividend と divisor が同符号ならば+,異符号ならば−. (2) 剰余 (div64_t::rem) ・rem == dividend - quot * divisor ・0 ≦ |rem| < divisor ・rem の符号は dividend と同じ. 2007/09/17(月) 作成 ─────────────────────────────────────*/ typedef struct { int64_t quot; int64_t rem; } div64_t; div64_t div64(int64_t dividend, int64_t divisor) { div64_t qr; Bool negativeQuotient = FALSE; // 最終的に qr.quot < 0 Bool negativeRemainder = FALSE; // 最終的に qr.rem < 0 if(divisor < 0) { divisor = (-divisor); negativeQuotient = !negativeQuotient; } if(dividend < 0) { dividend = (-dividend); negativeQuotient = !negativeQuotient; negativeRemainder = TRUE; } qr.quot = dividend / divisor; qr.rem = dividend - divisor * qr.quot; if(negativeQuotient) qr.quot = (-qr.quot); if(negativeRemainder) qr.rem = (-qr.rem); return qr; }
#include <assert.h> #include <stdlib.h> #include <stdio.h> /*───────────────────────────────────── 機能 :2つの無符号整数 m,n の最大公約数 (GCD) を求める. (ユークリッドの互除法を使用.) 入力 :m,n:任意の無符号整数値. 戻り値:GCD(m, n).ただし m または n の少なくとも一方が0ならば max(m, n). 参考 :(1) ユークリッドの互除法 (Wikipedia),英語版には再帰版と非再帰版の 擬似コードがある. (2) 末尾再帰 (tail recursion) の最適化をしてくれるコンパイラならば, 再帰版でもスタックを使わず,速度も落ちない (はず). 2007/10/20(土) 作成 2008/05/03(土) 戻り値の説明文を訂正. ─────────────────────────────────────*/ // unsigned long 用,非再帰版 unsigned long GcdUL_NonRecursive(unsigned long m, unsigned long n) { unsigned long r; while(n > 0) { r = m % n; m = n; n = r; } return m; } // unsigned long 用,再帰版 unsigned long GcdUL_Recursive(unsigned long m, unsigned long n) { return (n <= 0) ? m : GcdUL_Recursive(n, m % n); } /*───────────────────────────────────── 機能 :GcdUL_NonRecursive(),GcdUL_Recursive() のテスト. 2007/10/20(土) 作成 ─────────────────────────────────────*/ int main(void) { unsigned long m, n, gcd1, gcd2; for(;;) { fprintf(stderr, "m n: "); if(scanf("%lu %lu", &m, &n) != 2) break; gcd1 = GcdUL_NonRecursive(m, n); gcd2 = GcdUL_Recursive(m, n); assert(gcd1 == gcd2); if(gcd1 > 0) { assert((m % gcd1) == 0); assert((n % gcd1) == 0); } printf("GCD(%lu, %lu) = %lu\n", m, n, gcd1); } return EXIT_SUCCESS; } /*───────────────────────────────────── 実行例 (VC2003) ─────────────────────────────────────*/ D:\C\test\Math\GCD\Debug>GCD m n: 150 225 GCD(150, 225) = 75 m n: 1024 256 GCD(1024, 256) = 256 m n: 27 5 GCD(27, 5) = 1 m n: 1234567890 987654321 GCD(1234567890, 987654321) = 9 m n: 0 100 GCD(0, 100) = 100 m n: ^Z
/*───────────────────────────────────── 機能 :無符号整数の最下位の '1' のビットだけを抽出する. 入力 :uintValue:無符号整数. 戻り値:uintValue の純2進表現で最下位の '1' のビットだけを抽出したビットパ ターンを返す.別の言い方をすれば,uintValue=((奇数) * 2n) のとき, 2n を返す.uintValue=0 のときは0を返す. 使用例:AddressAlignmentOf() 2007/07/25(水) 考案 2007/07/26(木) 作成 2010/08/25(水) 最適化のため演算順序変更 ─────────────────────────────────────*/ #define LowestOneBit(uintValue) ((~(uintValue) + 1U) & (uintValue))
2010/08/25(水) 追記
Java にも同名の
Integer.lowestOneBit()
というのがあるらしい.
ただしこちらは2の補数を使っているので (-intValue & intValue).
解説:intの最も右側の1のビット以外を0にする (mwSoft)
#include <limits.h> // for UINT_MAX #include <stdlib.h> #include <stdio.h> /*───────────────────────────────────── 機能 :2の冪乗か否かを判定する. 入力 :uintValue:無符号整数. IsPowerOf2(uintValue) では uintValue≧0, IsPowerOf2_(uintValue) では uintValue>0. 戻り値:uintValue が2の冪乗のときそのときに限り真. 注意 :IsPowerOf2_(uintValue) は IsPowerOf2(uintValue) よりも少し速いが, uintValue=0 の場合も真を返すので uintValue>0 のときに限り使用すべき. 2009/11/22(日) 考案・作成 (IsPowerOf2()) 2011/06/29(水) 改名 (IsPowerOf2() → IsPowerOf2_()),IsPowerOf2() 追加. ─────────────────────────────────────*/ // 通常版 (uintValue≧0) #define IsPowerOf2(uintValue) (IsPowerOf2_(uintValue) && (uintValue > 0)) // 少し高速版 (ただし uintValue>0) #define IsPowerOf2_(uintValue) ((((uintValue) - 1U) & (uintValue)) == 0) /*───────────────────────────────────── 機能 :IsPowerOf2_() のテスト. 2009/11/22(日) 作成 2015/02/27(金) 10進表示を右揃えにした. ─────────────────────────────────────*/ void Test_IsPowerOf2_(unsigned startValue, unsigned endValue) { unsigned value = startValue; // value の表示桁数を自動決定する. const unsigned nHexDigits = HEXDIGITS(BitSizeOf(value)); // value の16進桁数 const unsigned nDecDigits = DECDIGITS(BitSizeOf(value)); // value の10進桁数 printf("Start: 0x%0*X %u\n", nHexDigits, value, value); for(;; value++) { if(IsPowerOf2_(value)) printf("0x%0*X %*u\n", nHexDigits, value, nDecDigits, value); if(value >= endValue) break; } printf("End: 0x%0*X %u\n", nHexDigits, value, value); } int main(void) { // ↓unsigned が64ビット以上の処理系では,UINT_MAX はやめた方が….(笑) Test_IsPowerOf2_(0, UINT_MAX); return EXIT_SUCCESS; } /*───────────────────────────────────── 実行結果 (VC2013) ─────────────────────────────────────*/ Start: 0x00000000 0 0x00000000 0 ← IsPowerOf2_(0) も真になるので注意! 0x00000001 1 0x00000002 2 0x00000004 4 0x00000008 8 0x00000010 16 0x00000020 32 0x00000040 64 0x00000080 128 0x00000100 256 0x00000200 512 0x00000400 1024 0x00000800 2048 0x00001000 4096 0x00002000 8192 0x00004000 16384 0x00008000 32768 0x00010000 65536 0x00020000 131072 0x00040000 262144 0x00080000 524288 0x00100000 1048576 0x00200000 2097152 0x00400000 4194304 0x00800000 8388608 0x01000000 16777216 0x02000000 33554432 0x04000000 67108864 0x08000000 134217728 0x10000000 268435456 0x20000000 536870912 0x40000000 1073741824 0x80000000 2147483648 End: 0xFFFFFFFF 4294967295
/*───────────────────────────────────── 機能 :ビット数から16進桁数を求める. 入力 :nBits:ビット数. 戻り値:nBits ビットの無符号整数を16進表示するのに必要な最大桁数. 2010/03/27(土) 作成 ─────────────────────────────────────*/ // どっちでもよい. #define HEXDIGITS(nBits) ICEIL(nBits, 4U) #define HEXDIGITS(nBits) (((nBits) + 3U) >> 2) /*───────────────────────────────────── 機能 :ビット数から8進桁数を求める. 入力 :nBits:ビット数. 戻り値:nBits ビットの無符号整数を8進表示するのに必要な最大桁数. 2010/03/27(土) 考案・作成 ─────────────────────────────────────*/ #define OCTDIGITS(nBits) ICEIL(nBits, 3U) /*───────────────────────────────────── 機能 :ビット数から10進桁数を求める. 近似式なのでビット数が大きいと誤差が出る可能性があるが,とりあえず Excel で試算してみると256ビットまでは大丈夫なことは確認した. 入力 :nBits:ビット数.とりあえず nBits=1〜256 としておく.ただし16ビット 処理系 (nBitsが16ビット整数型) の場合はオーバーフローしてしまうので, 16ビット無符号整数の場合は nBits=1〜53 (≦ UINT_MAX / 1233), 16ビット符号付整数の場合は nBits=1〜26 (≦ INT_MAX / 1233). 戻り値:nBits ビットの無符号整数を10進表示するのに必要な最大桁数. 参考 :(1) 1233/4096 は log10(2)=0.3010299… の近似値. (2) 数学的に厳密な計算式および近似式は, DECDIGITS(n) = floor(log10(2n−1)) + 1 ≒ floor(n * log10(2)) + 1 ≒ floor(n * 1233 / 4096) + 1. 2010/03/27(土) 考案・作成 2011/01/18(火) 高速化のため log10(2) の近似値を変更. (301/1000 → 1233/4096,分母を2の冪乗にした.) 2011/05/22(日) 数学的に厳密な計算式を追記. 2012/08/04(土) 説明に近似式を追加,それに合わせて定義変更 (結果に影響なし). 2012/08/09(木) 16ビット処理系の場合の注意を追記. ─────────────────────────────────────*/ #define DECDIGITS(nBits) ((((nBits) * 1233U) >> 12) + 1U) /*───────────────────────────────────── 機能 :{OCT,DEC,HEX}DIGITS() のテスト. 2010/03/27(土) 作成 ─────────────────────────────────────*/ #if defined(_MSC_VER) && (_MSC_VER < 1400) // VC2003 では unsigned long long が使えるのに %ll が使えない. #define LLFORMAT "I64" #else /* defined(_MSC_VER) && (_MSC_VER < 1400) */ #define LLFORMAT "ll" #endif /* defined(_MSC_VER) && (_MSC_VER < 1400) */ #define TEST_DIGITS(xxxDigits, valueFormat) \ nDigits = sprintf(buf, "%" LLFORMAT valueFormat, maxValue); \ assert(nDigits == xxxDigits(nBits)); \ printf("nBits:%u %s:%u maxValue:%s\n", nBits, #xxxDigits, nDigits, buf); void DigitsTest(void) { uint64_t maxValue = 0; // nBits ビット無符号整数で表現可能な最大値 uint64_t prevValue; unsigned nBits = 0; unsigned nDigits; char buf[64]; for(;;) { nBits++; prevValue = maxValue; maxValue = (maxValue << 1) | 1U; // 万一 uint64_t に無効ビットがあっても正しい位置で終了する. if(maxValue == prevValue) break; TEST_DIGITS(OCTDIGITS, "o") // OCTDIGITS() の結果が正しいか確認する. TEST_DIGITS(DECDIGITS, "u") // DECDIGITS() の結果が正しいか確認する. TEST_DIGITS(HEXDIGITS, "X") // HEXDIGITS() の結果が正しいか確認する. } }
/*───────────────────────────────────── 機能 :無符号整数型の MSB だけを抽出するためのビットマスクを返す. (無効ビットがある型でもたぶん OK.符号付整数型は不可.) 入力 :uintType:無符号整数型. 戻り値:MSB だけが1となる uintType 型の値. (例) MSBMASK(uint8_t) → 0x80U MSBMASK(uint16_t) → 0x8000U MSBMASK(uint32_t) → 0x80000000U MSBMASK(uint64_t) → 0x8000000000000000U 注意 :(1) 次の方法では,uintType に無効ビットがある場合に対応できない. #define MSBMASK(uintType) \ ((uintType)1 << (BitSizeOf(uintType) - 1)) (2) 符号付整数型では,右シフトの処理系依存性のためうまくいかない. また処理系によっては (途中結果または最終結果が) トラップ表現に 引っかかる可能性がある.そもそもトラップ表現のある符号付整数型で MSB だけのビットパターンを抽出しようという考え自体が誤り. 2010/03/26(金) 考案・作成 ─────────────────────────────────────*/ #define MSBMASK(uintType) \ ((uintType)((uintType)~(uintType)0 - ((uintType)~(uintType)0 >> 1)))
/*───────────────────────────────────── 機能 :複数の数値またはポインタが昇順になっているか否かを判定する. 入力 :value1〜:数値型,またはオブジェクトへのポインタ型. (不完全型や関数へのポインタは不可) 戻り値:value1 ≦ value2 ≦ value3 … のときそのときに限り真. 2007/10/02(火) 作成 (ValueInRange) 2007/10/03(水) 作成 (ValuesInOrder4) 2007/10/06(土) 改名 (ValueInRange → ValuesInOrder3) ─────────────────────────────────────*/ #define ValuesInOrder3(value1, value2, value3) \ (((value1) <= (value2)) && ((value2) <= (value3))) #define ValuesInOrder4(value1, value2, value3, value4) \ (((value1) <= (value2)) && ((value2) <= (value3)) && ((value3) <= (value4)))
/*───────────────────────────────────── 機能 :数値またはポインタが区間 (範囲) 内にあるか否かを判定する. ・ValueInRangeInclusive(),ValueInRange():閉区間専用. ・ValueInRangeExclusive():開区間専用. ・ValueInRangeOp():(半)開区間にも使用可能. 入力 :数値型,またはオブジェクトへのポインタ型. (不完全型や関数へのポインタは不可) (1) minValue:最小値. (2) value:値. (3) maxValue:最大値. (4) infValue:下限値 (infinimum). (浮動小数型で infValue<value の場合,infValue は最小値ではなく下限値.) (5) supValue:上限値 (supremum). (浮動小数型で value<supValue の場合,supValue は最大値ではなく上限値.) (6) op1,op2:不等号 (< または <=). 戻り値:(1) ValueInRange: minValue ≦ value ≦ maxValue のときそのときに限り真. (2) ValueInRangeOp: infValue op1 value op2 supValue のときそのときに限り真. 2007/10/02(火) 作成 (ValueInRange) 2007/10/06(土) 改名 (ValueInRange → ValuesInOrder3) 2010/06/27(日) 作成 (ValueInRangeOp) 2015/08/09(日) 作成 (ValueInRangeExclusive) 改名 (ValueInRange → ValueInRangeInclusive) ─────────────────────────────────────*/ // minValue と maxValue を範囲に含む (inclusive). #define ValueInRangeInclusive(minValue, value, maxValue) \ ValuesInOrder3(minValue, value, maxValue) #define ValueInRange ValueInRangeInclusive // 旧名 // infValue と supValue を範囲に含まない (exclusive). #define ValueInRangeExclusive(infValue, value, supValue) \ (((infValue) < (value)) && ((value) < (supValue))) #define ValueInRangeOp(infValue, op1, value, op2, supValue) \ (((infValue) op1 (value)) && ((value) op2 (supValue))) // 例 if(ValueInRangeOp(10.0, <=, x, <, 20.0)) { … } // 左閉右開区間 [10.0, 20.0)
次のマクロは,任意の配列 array[] の要素数を返す (もちろん不完全型は不可).
#define ArraySizeOf(array) (sizeof(array) / sizeof(array[0]))
このマクロを使用すると,配列に要素を追加/削除した場合でも, 別途要素数を書き替える必要がなくなり,ソースの保守性が向上する.
他人様の書いたソースを見ていると, 特定の配列の要素数を取得するマクロを定義しているのを時々見かける. 例えば int 型要素の配列 IntArray[] の場合は次のような具合.
#define N_ELEMENTS (sizeof(IntArray) / sizeof(int))
せっかくここまでやるのなら, どうしてもう一歩進めて任意の配列に使用できるようにしないのか不思議.
■使用例
const char * const StringArray[] = { "zero", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "ten", }; void PrintStrings(void) { unsigned i; for(i = 0; i < ArraySizeOf(StringArray); i++) printf("StringArray[%u] = \"%s\"\n", i, StringArray[i]); }
■同様のマクロ (有名どころ)
■図解 (2011/09/25(日) 追記)
配列の物理的なデータ構造 (メモリ上でどういうビットパターンになっているか) がわかっていれば,このマクロは割り算 (包含除) を習ったばかりの小学3年生でも思いつくはず.
「C 配列 要素数」などで検索して来る人が毎日何人もいるということは, 配列の物理的な構造を理解していない (配列を C/C++ の文法としてしか理解していない (教わらない?)) 人が多いということだろう.(ひょっとして教える方もわかっていない?)
#define N 32 int array[N] = { 0x00010203, 0x04050607, … , 0x7C7D7E7F }; ●array[] の (C/C++ から見た) 物理的なデータ構造 (ビットパターン) (CHAR_BIT=8ビット,int=4バイト・リトルエンディアンの CPU の場合) 1バイト (CHAR_BITビット) array, ←────────→ &array[0] →┏━━━━━━━━━┓┬─────────┬ ┃ 00000011 ┃↑ ↑ ┠ ┨│ │ ┃ 00000010 ┃│ │ array[0] ┠ ┨│sizeof(array[0]) │ ┃ 00000001 ┃│ = 4 │ ┠ ┨│ │ ┃ 00000000 ┃↓ │ &array[1] →┠─────────┨┴ │ ┃ 00000111 ┃ │ ┠ ┨ │ ┃ 00000110 ┃ │ array[1] ┠ ┨ │ ┃ 00000101 ┃ │ ┠ ┨ │ ┃ 00000100 ┃ │ &array[2] →┠─────────┨ sizeof(array) │ : : = 4 * N │ : : │ : : │ : : │ : : │ : : │ : : │ &array[N-1] →┠─────────┨ │ ┃ 01111111 ┃ │ ┠ ┨ │ ┃ 01111110 ┃ │ array[N-1] ┠ ┨ │ ┃ 01111101 ┃ │ ┠ ┨ │ ┃ 01111100 ┃ ↓ &array[N] →┗━━━━━━━━━┛──────────┴ array:配列の先頭要素 array[0] の先頭アドレス,または配列全体を表す. (参考:新・闘わないプログラマ No.327 配列の先頭アドレス) &array[i]:array[i] の先頭アドレス &array[N]:array の終端アドレス
ArraySizeOf(array)
の array は配列型変数でなければならず,
配列へのポインタでは正しい値を返しません.
例えば次のような場合です.
/* 上記図解のコードの続き */ int *pArray = array; /* 2007/06/24(日) 移植性の問題を修正 */ #if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) /* C99 以後 */ printf("%zu\n", ArraySizeOf(pArray)); /* 誤 */ printf("%zu\n", ArraySizeOf(array)); /* 正 */ #else /* defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) */ /* C99 より前 */ printf("%lu\n", (unsigned long)ArraySizeOf(pArray)); /* 誤 */ printf("%lu\n", (unsigned long)ArraySizeOf(array)); /* 正 */ #endif /* defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) */
うまくいかない理由は,sizeof(pArray) が配列のサイズ (バイト数)
ではなく,配列へのポインタのサイズだからです.
(建物の大きさと,その住所の文字数 (アドレスのサイズ)
には何の関係もないでしょ?)
・ポインタは4バイトとする. ・array の先頭アドレスを仮に 0x8090A0B0 とする. pArray(略図) ┏━━━━━┓ array ┃ ・──╂──────────→┏━━━━━━┓┬ ┗━━━━━┛ 0x8090A0B0┃ array[0] ┃↑ || ┠──────┨│ ∨ ┃ array[1] ┃│ pArray(詳細) ┠──────┨│ ┏━━━━━┓┬ ┃ array[2] ┃│ ┃ 11010000 ┃↑ ┠──────┨│sizeof(array) ┠ ┨│ : :│ ┃ 10100000 ┃│sizeof(pArray) : :│ ┠ ┨│ = 4 : :│ ┃ 10010000 ┃│ ≠ sizeof(array) : :│ ┠ ┨│ ┠──────┨│ ┃ 10000000 ┃↓ ┃ array[N-1] ┃↓ ┗━━━━━┛┴ ┗━━━━━━┛┴
また,ArraySizeOf() を関数にすることはできません.第1の理由は, 関数に渡されるのが配列そのものではなく,配列の先頭要素へのポインタだからであり, 上記の場合と同じ問題に直面するからです.第2の理由は, 任意の配列に使えるようにしようとすると,引数の型が定まらなくなるからです.
なぜわざわざこんな注意書きを追記したかというと,ついさっき
ArraySizeOf() の一歩手前まで行きながら,
最後の一歩を間違えたり,行き過ぎたりしている(いた)人を見つけたので.
C/C++ の多次元配列は「配列の配列」なので,例えば多次元配列 array[N1][N2][N3] の要素数 N1,N2,N3 は次のようにすれば得られます.
ArraySizeOf(array) → N1 ArraySizeOf(array[0]) → N2 ArraySizeOf(array[0][0]) → N3
■2009/01/11(日) 追記
次の各マクロは,要素の型と個数を指定して配列を確保します. alloca(),malloc(),calloc(),realloc() を直接使うよりも便利です. (ただし alloca() は非標準なので処理系依存.)
#define ALLOCA(type, n) ((type*)alloca(sizeof(type) * (n))) #define MALLOC(type, n) ((type*)malloc(sizeof(type) * (n))) #define CALLOC(type, n) ((type*)calloc((n), sizeof(type))) #define REALLOC(type, n, p) ((type*)realloc((p), sizeof(type) * (n)))
次の各マクロは,配列の先頭を指すポインタ arrayPtr と要素数 n を引数として配列を確保します. また配列要素の型を変更する場合は,arrayPtr の型だけを変更すればよく, サイズの計算式を変更する必要がありません.
#define ARRAYALLOCA(arrayPtr, n) ((arrayPtr) = alloca(sizeof((arrayPtr)[0]) * (n))) #define ARRAYMALLOC(arrayPtr, n) ((arrayPtr) = malloc(sizeof((arrayPtr)[0]) * (n))) #define ARRAYCALLOC(arrayPtr, n) ((arrayPtr) = calloc((n), sizeof((arrayPtr)[0]))) #define ARRAYREALLOC(newArrayPtr, oldArrayPtr, n) \ ((newArrayPtr) = realloc((oldArrayPtr), sizeof((newArrayPtr)[0]) * (n)))
使用例:
#define OK 0 /* 正常終了 */ #define FAIL (-1) /* エラー */ double *SineTable; /* 配列の先頭を指すポインタ */ int MakeSineTable(unsigned n, double initialAngle, double stepAngle) { double angle; unsigned i; /* 配列 SineTable[n] を確保する.*/ if(ARRAYMALLOC(SineTable, n) == NULL) return FAIL; /* メモリ不足 */ /* 配列の内容 (正弦関数表) を記入する.*/ for(angle = initialAngle, i = 0; i < n; i++, angle += stepAngle) SineTable[i] = sin(angle); return OK; /* 成功 */ }
1991/05/11(土) 考案 (*ALLOC()) |
1995/12/16(土) 考案 (ARRAY*ALLOC()) |
/*───────────────────────────────────── 引数 :array:配列型変数. 戻り値:配列の直後のアドレス. 2008/06/24(火) 作成 ─────────────────────────────────────*/ #define ArrayEnd(array) ((array) + ArraySizeOf(array))
使用例:
int IntArray[N]; long ArraySumTest(void) { const int *element = IntArray; const int * const arrayEnd = ArrayEnd(IntArray); long sum = 0; while(element < arrayEnd) sum += *element++; return sum; }
/*───────────────────────────────────── 引数 :array:配列型変数. 戻り値:配列の最後の要素のアドレス. 2009/09/07(月) 作成 2009/09/21(月) 改定 ─────────────────────────────────────*/ #define ArrayLast(array) ((array) + (ArraySizeOf(array) - (size_t)1))
構造体のメンバ数の取得方法を知りたい方はこちら.
C 標準ライブラリのヘッダファイル stddef.h で定義されているマクロ offsetof()
は,type 型構造体のメンバ member の,先頭からのバイト・オフセットを返します.(図)
次にその定義の一例を示します.
// C専用 (stddef.h で定義).C++用はこちら. #define offsetof(type, member) ((size_t)&((type*)0)->member)
このマクロは,昔々 (1987年頃) UNIX 4.1BSD のヘッダファイル struct.h の中で見つけ,便利そうだと思って自分のヘッダファイルに取り込んでずっと使ってきたのですが, 今では C 標準ライブラリのマクロに昇格したんですね (感慨).
■同様のマクロ (有名どころ)
■余談
小耳に挟んだ話によると,ごく一部の CPU (Cray Y-MP など) には, なぜか構造体の先頭オフセットが0でないものがあるらしい. しかしそういうのはワードマシンのごく一部だけで,現在大多数を占めているバイトマシンではありえないだろう.
type 型の構造体変数が定義されていれば, そのメンバ member のオフセット offset は次のようにして求められる.
type t; #define offset ((size_t)((char*)&t.member - (char*)&t))
しかしこの方法では次の問題がある.
このためサイズが offset に依存する配列を宣言したい場合などに都合が悪い.
(例) char buffer[offset + sizeof(…)]; // offset は定数式ではないのでエラー
もし t がアドレス0に配置される (つまり &t == 0) ならば, offset の計算式は次のようになり,引き算をしなくてすむ.
offset = (size_t)((char*)&t.member - (char*)&t) // &t == 0 = (size_t)(ptrdiff_t)&t.member = (size_t)&t.member
ここで変数 t は,そのアドレス &t を計算に使っているだけであり,t の実体にアクセスする必要はない. だから t を *(type*)0 に置き換えてもよく,そうすれば t が不要になる.
offset = (size_t)&t.member = (size_t)&((*(type*)0).member) = (size_t)&(((type*)0)->member)
これは定数式なので,配列宣言で使用することもできる.
(例) char buffer[offsetof(type, member) + sizeof(…)]; // OK
ネット上ではそのような書き方をしている人を時々見かける.「NULL は 0 として定義されているんだから同じじゃないか」と言いたい人もいるだろう.確かにコンパイルしてしまえば全く同じだから,どっちの書き方をしても動作上は問題ない.しかし考え方としては完全に間違い. (下記の「内包と外延」を参照)
NULL は「何も指さないポインタ」という意味なので,((type*)NULL)->member
という式は無意味 (概念上矛盾している).
(世間では「矛盾塊」というらしい.)
他方, offsetof での (type*)0 は上記の説明のとおり「type 型のデータがアドレス0に配置されている (と見なす)」という意味であり,さらにこの0は「0を引き算しても結果が変わらない (加法単位元)」という意味での0である.
NULL を0として定義するのは単に C/C++ の「便宜上のお約束」であって, 実際にはアドレス0に有効なデータが存在する (つまり (type*)0 が有効なポインタである) 場合もある.逆に,昔のコンピュータには NULL≠0 である機種も存在したらしい (下記の comp.lang.c FAQ 5.17 参照).
メモリ保護機構のある OS では,アプリケーションプログラムが (論理) アドレス0にアクセスしようとしてもアクセス違反になるが, 組込み系や OS カーネルでは物理アドレス0にアクセスできる.そこにはメモリが実在しているだけでなく,CPU にとって特別な意味を持つデータやプログラム (ハードウェアレベルで仕様が決まっている) が配置されているのが普通だからである.
■参考
内包と外延ですぐ出てくる話が,宵の明星と明けの明星の話です.つまり内包としては,古来人間の認識としては両者は別物ですが,外延としては,実は同一物(すなわち金星)だった.
「明けの明星」も「宵の明星」も同一の個体 (引用者注:金星) を指示していて、同じ外延を有している。しかし、直観的にいってそれらが同じ (意味) をもっているとはいえない。この場合の (意味) とされるのが、固有名の「内包」である。(中略) つまり、「明けの明星」の内包は (明け方の東の空に明るく輝く星である) という条件である。そして、たまたまその条件を満たすものが金星、 「明けの明星」の外延であるということになる。(中略) 他の可能世界においては、それらは別の惑星を指示するかもしれない。
■抄訳
- Prime 50 series は NULL ポインタとしてセグメント 07777(8進数),オフセット 0 を使用した.8086 (元祖 x86 (16bit CPU)) の segment:offset 形式ポインタ記法で書けば 0FFF:0000.少なくとも PL/I で用いられた.NULL=0 を前提として書かれた既存の C コードに対応するため,後の機種では 0000:0000 を使用するようになったが,そのため新たに TCNP (Test C Null Pointer) 命令を追加せざるを得なかった.
- Honeywell Bull 社のメインフレームのいくつかの機種では (内部用) NULL ポインタとして 06000(8進数)=0x0C00 を使用した.
- CDC Cyber 180 Series のポインタは ring,segment,offset からなる48ビットだった.一般ユーザ (ring 11) にとって NULL ポインタは 0xB00000000000 だった.(8086 風に書けば ring:segment:offset=B:0:0.)
また同社の旧機種は1の補数を使用していたが,ワードの全ビットが1 (つまり -0) の場合は特別なフラグ (ポインタとして使用する場合は NULL) として扱われた.- Symbolics Lisp Machine は C の NULL ポインタとして <object, offset>=<NIL, 0> というペアを用いた.
次のマクロは,type 型構造体のメンバ member のサイズを返します.
#define MemberSizeOf(type, member) sizeof(((type*)0)->member)
このマクロも,UNIX 4.1BSD のヘッダファイル struct.h の中で見つけたものを元にしています.
■同様のマクロ (有名どころ)
次のマクロは,type 型構造体のメンバ memberArray が配列のとき,その要素数を返します.
#define MemberArraySizeOf(type, memberArray) ArraySizeOf(((type*)0)->memberArray)
2006/07/10(月) 作成 |
2007/05/16(水) 改定 |
次のマクロは,type 型構造体のメンバ member のアドレス pMember が与えられた時, 構造体のベースアドレスを返します.
#define StructBaseFromOffset(type, offset, pMember) \ ((type*)((char*)(pMember) - (offset))) #define StructBase(type, member, pMember) \ StructBaseFromOffset((type), offsetof(type, member), (pMember))
このマクロも,UNIX 4.1BSD のヘッダファイル struct.h の中で見つけたものを元にしています.
■同様のマクロ (有名どころ)
2007/06/24(日) 追記
仕様上はポインタ演算に問題がありますが,
実用上は多分問題ないと思うのでそのままにします.
(StructMember() も同様)
C/C++ の文脈で「動的構造体」というと, 世間では malloc や new で動的に確保した (静的) 構造体のことを意味するようですが, ここでは別の意味に使います. C/C++ に限らずコンパイラ言語では, 構造体の定義はコンパイル時に (静的に) 決定されます. これに対しここでいう「動的構造体」とは, コンパイル時にはその構造が確定しておらず, プログラムの実行時に構造が決定される構造体を意味します. 例えば可変長配列をメンバとして含んだり, 特定のメンバを含むか否かが実行時に決定されるような場合です. 無理やりC言語風に書けば,
struct MyDynamicStruct(size_t n, int coordType, type_t valueType, size_t maxStrLen) { int number[n]; // 可変長配列 // 実行時に選択されるメンバ switch(coordType) { case INTEGER: // 3次元座標は符号付整数 int x, y, z; break; case FLOAT : // 3次元座標は単精度浮動小数 float x, y, z; break; case DOUBLE : // 3次元座標は倍精度浮動小数 double x, y, z; break; default: ; // 座標なし } valueType value; // 実行時に型が指定されるメンバ char string[maxStrLen + 1]; // 可変長配列 };
つまり静的構造体はプログラマがその構造を決定するのに対し, 動的構造体はプログラムが実行時に構造を決定します.動的構造体は,C言語の struct 構文で宣言される (静的) 構造体とは無関係です.
次のマクロは,先頭アドレスを base とする動的構造体において, その先頭から offset バイトの位置にある type 型メンバのアドレスを返します. つまり,静的構造体での &base->member に相当する値です.
#define StructMember(type, base, offset) ((type*)((char*)(base) + (offset)))2007/06/24(日) 追記
仕様上はポインタ演算に問題がありますが,
実用上は多分問題ないと思うのでそのままにします.
(StructBase() も同様)
次のマクロは,データ型 type のサイズをビット数で取得します. CHAR_BIT は limits.h の中で定義されています.
#include <limits.h> #define BitSizeOf(type) (sizeof(type) * CHAR_BIT)■注意 (2007/06/24(日) 誤記訂正)
私はいまだに使ったことはありませんが, 有効ビット数が中途半端な整数型も存在するそうです. そのようなデータ型では,実際の有効ビット数と BitSizeOf(type) の値が一致しない場合があります.
PIC マイコン用Cコンパイラ
(MPLAB© C18C コンパイラ) にも,2の冪乗でないサイズ (24bit)
の整数型があります (リンク先の11ページ参照).
しかもその型名がなんと,short long 型!! (どっちやねん!)
この型名は独自拡張であり,Cの規格上は違反だそうです.
ちなみにアラインメントは書かれてないけど,何バイトなんだろう?
次のマクロは,データ型 type のアラインメントを取得します. 「アラインメントって何?」という方はこちらへどうぞ.
#define AlignmentOf(type) offsetof(struct { char a; type b; }, b)
このマクロは,構造体の先頭に1バイト (char 型) のダミーメンバ a を, その次に type 型のメンバ b を配置し, b のオフセットを調べることで type の実際のアラインメントを取得します.
なお,このマクロは C 専用です.
C++ では構文エラーになり,使用できません.
(C++ 用は下記の追記参照.)
Microsoft C では,同様の機能を持つ演算子 __alignof() が使用できます. ただし AlignmentOf() と異なる値を返す場合があります (バグというわけではなく,そういう仕様のようです).
GCC でも同様の機能を持つ演算子 __alignof__ が使えるようですが,実験はまだ行っていません.
次の2つのマクロは,オフセット値 offset がアラインメントの倍数になるように切り上げた値を返します.
#define AlignOffset(offset, alignment) \ ((size_t)((offset) + ((alignment) - 1)) & ~(size_t)((alignment) - 1)) #define AlignType(offset, type) AlignOffset(offset, AlignmentOf(type))
AlignOffset() には,アラインメント alignment を直接指定します. alignment は2の冪乗 (1,2,4,8,…) でなければなりません. AlignType() には,アラインメントを直接指定する代わりに, 型名 type を指定します.
■同様のマクロ (有名どころ)
■参考
/*───────────────────────────────────── 機能 :データ (バイト列) をバイト逆順にする. 入力 :(1) data:データの先頭アドレス. (2) nBytes:バイト列のサイズ (バイト数). 入出力:((unsigned char*)data)[0 〜 nBytes-1]:データ. 1990/05/06(日) 初版 2007/04/16(月) 改定 2009/05/23(土) トラップ表現のある処理系で問題になるかもしれないので, char* → unsigned char* に変更. (0x80 が格納されているバイトアドレスを (signed) char* で参照 した途端,何かが起きる! … かもしれない.) ─────────────────────────────────────*/ void ByteReverse(void *data, size_t nBytes) { unsigned char *low = (unsigned char*)data; unsigned char *high = low + nBytes; unsigned char temp; while(--high > low) { // *low と *high を交換する. temp = *low; *low++ = *high; *high = temp; } }
/*───────────────────────────────────── 機能 :アドレス (値) のアラインメントを取得する.つまり,そのアドレスが最大 何バイトのアラインメントに適合するかを調べる. 入力 :address:アドレス. 戻り値:address のアラインメント.address=0 (NULL) ならば0. 使用例:アラインメントを考慮した memcpy() の高速版 (気が向いたら公開予定). 2007/07/25(水) 考案 2007/07/26(木) 作成 ─────────────────────────────────────*/ #define AddressAlignmentOf(address) LowestOneBit((size_t)(address))
別ページに飛びます.
別ページ (ネットで第1バイトの巧妙な判定方法を見つけたので追加.)
次のマクロは,ASCII 図形文字に対応する ASCII 制御文字コードを返します.
(ソース文字コードが ASCII (または亜種) の処理系専用.)
#define CTRL(c) ((c) ^ 0x40)
CTRL('@') 〜 CTRL('_') は 0x00(NUL) 〜 0x1F(US) に,
CTRL('?') は 0x7F(DEL) になります.
(ASCII コード表
(縦32×横4 のもの),
JIS X 0211 制御コード表
と見比べてみてください.)
例えば,CTRL+C の文字コード (0x03) は CTRL('C') と書けます (注意:CTRL('c') は不可).
次の関数は,文字列終端の '\0' (ワイド文字版は L'\0') のアドレスを返す.
(Win32 API の命名規則に倣って,関数名の最後の文字を 'A' (char 版) または 'W'
(wchar_t 版) にしている.)
/*───────────────────────────────────── 書式 :char *StringEndA(const char *string); wchar_t *StringEndW(const wchar_t *string); 戻り値:文字列 string の終端 NUL のアドレス. 用途 :string の後ろに別の文字列を連結する場合や,string 直後の空きメモリを 使いたい場合などに使用する. 1991/09/06(金) 作成 (StringEnd) 2005/05/23(月) 作成 (StringEndW),改名 (StringEnd → StringEndA) 2009/05/23(土) IMPLEMENT_StringEnd() で定義を共通化. ─────────────────────────────────────*/ #define IMPLEMENT_StringEnd(funcName, charType) \ charType *funcName(const charType *string) \ { \ while(*string != (charType)0) string++; \ return (charType*)string; \ } // StringEndA() を定義する. IMPLEMENT_StringEnd(StringEndA, char) // StringEndW() を定義する. IMPLEMENT_StringEnd(StringEndW, wchar_t)
「こんな関数,一体何の役に立つの?」という幻聴 (?) がするので, 使用例を挙げておく.
■使用例1:複数回の sprintf() の出力を連結する. (バッファオーバーランに注意.)
char buffer[BUFSIZE]; char *dest = buffer; sprintf(dest, "…", …); sprintf(dest = StringEndA(dest), "…", …); : sprintf(dest = StringEndA(dest), "…", …); printf("%s\n", buffer /* dest じゃないよ */); // 連結した文字列を出力する.
■使用例2:あるディレクトリ内の全エントリ (ファイル,サブディレクトリなど) 名をフルパス名で出力する.
// 細かい仕様 (パス名の最大長および区切り文字) は一応 Windows 用. const char * const directory = (ディレクトリのフルパス名); const char *entry; // ディレクトリエントリ名 char *dest; char pathName[MAX_PATH]; // パス名用バッファ strcpy(pathName, directory); dest = StringEndA(pathName); // 必要ならば,directory の最後に区切り文字 '\\' を追加する. if(必要) *dest++ ='\\'; while((entry = (directory から次のエントリ名を取得)) != NULL) { strcpy(dest, entry); // pathName の必要部分だけを書き替える. printf("%s\n", pathName); // entry をフルパスで出力する. }
この例だけだとあまり有難みがわからないが,
ディレクトリツリーをスキャンしながらすべてのフルパス名を列挙する場合に,
パス名の必要部分のみを書き替えるようにすると効率が良い
(エントリごとにフルパス名全体を作り直すのは無駄が多い).
StringEnd() は,その書き替えを行う先頭位置を探すのに使用できる.
(実際に StringEnd() が返すのはその1文字前 ('\\') の位置
(つまりサブディレクトリ名の終端 NUL).)
/*───────────────────────────────────── 概要 :マクロ展開結果を文字列化する. 引数 :macroArg:マクロ呼び出しを含む式など. (マクロの引数にできるものなら何でもよい.) 戻り値:macroArg をマクロ展開した結果を文字列リテラルにしたもの. ─────────────────────────────────────*/ #define MacroExpandedStringAux(x) #x #define MacroExpandedString(macroArg) MacroExpandedStringAux(macroArg) // マクロ展開結果を out に出力する. #define PrintMacroExpansion(macroArg, out) \ fprintf((out), "MacroExpandedString(%s) -> \"%s\"\n", \ #macroArg, MacroExpandedString(macroArg))
■使用例1
PrintMacroExpansion(MAX_PATH + 1, stdout); PrintMacroExpansion(fgetc(stdin) == EOF, stdout); PrintMacroExpansion(assert(x), stdout); PrintMacroExpansion(MacroExpandedString(MAX_PATH + 1), stdout); PrintMacroExpansion(x + y, stdout); // マクロ呼び出しを含まない場合 // 実行結果 (VisualC++ 2012) MacroExpandedString(MAX_PATH + 1) -> "260 + 1" MacroExpandedString(fgetc(stdin) == EOF) -> "fgetc((&__iob_func()[0])) == (-1)" MacroExpandedString(assert(x)) -> "(void)( (!!(x)) || (_wassert(L"x", L"test.cpp", 85), 0) )" MacroExpandedString(MacroExpandedString(MAX_PATH + 1)) -> ""260 + 1"" MacroExpandedString(x + y) -> "x + y" // マクロ呼び出しを含まない場合 (そのまま)
■使用例2
コンパイル中にマクロの展開結果を表示する.(VC2012)
#pragma message("_MSC_VER=" MacroExpandedString(_MSC_VER)) #pragma message("_MSC_FULL_VER=" MacroExpandedString(_MSC_FULL_VER)) #pragma message("NTDDI_VERSION=" MacroExpandedString(NTDDI_VERSION)) #pragma message("WINVER=" MacroExpandedString(WINVER)) #pragma message("_WIN32_WINNT=" MacroExpandedString(_WIN32_WINNT)) #pragma message("_WIN32_IE=" MacroExpandedString(_WIN32_IE)) #pragma message("__FILE__=" MacroExpandedString(__FILE__)) #pragma message("__LINE__=" MacroExpandedString(__LINE__)) // コンパイル中の表示 1>------ ビルド開始: プロジェクト: CompileTest, 構成: Debug Win32 ------ 1> CompileTest.cpp 1> _MSC_VER=1700 1> _MSC_FULL_VER=170060610 1> NTDDI_VERSION=0x06010000 1> WINVER=0x0601 1> _WIN32_WINNT=0x0601 1> _WIN32_IE=0x0800 1> __FILE__="..\\CompileTest.cpp" 1> __LINE__=15 1> CompileTest.vcxproj -> D:\C\CompileTest\VC2012\Win32\Debug\CompileTest.exe ========== ビルド: 1 正常終了、0 失敗、0 更新不要、0 スキップ ==========
このマクロは,マクロ定数の値を文字列化するトリッキーな方法として知られている. 「macro stringize」で検索すると STR(x) とか STRINGIZE(x) などという名前が付けられていることが多いが,こんなあいまいな名前だと x がどういう条件ならば使えるのかよくわからない.それに,検索したページで示されている実行例は x が単純なマクロ定数の場合ばかりだが,実際にはマクロ定数や関数マクロを含む式なら何でもよい.
というわけでこのマクロは MacroExpandedString() という名前にした.
C89 以降なら動作が保証されているらしい.
実際には macroArg は式に限らず,マクロの引数にできるものならば何でもよい.
また macroArg にはマクロを含まなくてもよいが,その場合は macroArg
がそのまま文字列になるだけなので何もうれしくない.(上の実行例の "x + y" の場合)
2012/11/25(日) 作成 |
2013/10/27(日) 使用例2を追記 |
■同様のマクロ (2016/10/22(土) 追記)
OpenCV の opencv2/core/version.hpp
では,バージョン番号を文字列化するのに CVAUX_STR( )
というマクロが使われている.
CVAUX_STRW( ) はワイド文字列バージョン.
次のマクロは,任意の型の変数 var をバイナリファイルへの出力ストリーム out に書き出し, 成功したときそのときに限り真を返します.
#define WriteVar(var, out) (fwrite(&(var), 1, sizeof(var), (out)) == sizeof(var))
var がポインタ型やポインタを含む型であっても書き出すことはできますが, そうする意味がありません.
このマクロを使うに当たり, データ型の選択やエンディアンの変換などを行わなければ, 作成されたバイナリファイルは CPU 依存,処理系依存となります.
次のマクロは,任意の型の変数 var をバイナリファイルの入力ストリーム in から読み込み, 成功したときそのときに限り真を返します.
#define ReadVar(var, in) (fread(&(var), 1, sizeof(var), (in)) == sizeof(var))
改行コードが混在するテキストファイルを読む方法の参考用. 改行コードを統一して出力する関数の例です.
(2007/07/29(日) 追記)
「改行コード 判定」などで検索してくる人が時々いるので,
改行コードの種類別出現回数を数えるようにしました.
(でも判定しようとしても,混在してたら判定不能というオチがつくだけなんで全然うれしくない.
そんなことするより,どんな改行コードでも読めるようにしておく方がいい.)
●テキストファイルの文字コードに関する前提 (2007/07/05(木) 追記)
#include <errno.h> #include <stdio.h> #include <string.h> #if defined(TEXTFILE_IS_ASCII) // テキストファイルの文字コードが ASCII (またはその亜種や拡張) の場合 #define CR 0x0D #define LF 0x0A #elif defined(TEXTFILE_IS_EBCDIC) // テキストファイルの文字コードが EBCDIC 系の場合 #define CR 0x0D #define LF 0x25 #endif /* defined(TEXTFILE_IS_*) */ #define OK 0 #define FAIL (-1) /*───────────────────────────────────── 機能 :改行コード (CR,CRLF,LF) が混在するテキストファイルを読み,統一した 改行コード eol に変換して別のファイルに出力する.テキストファイル内 の文字を次のように変換する. CR LF → eol CR (LF が後続しない) → eol LF (CR が先行しない) → eol 入力 :(1) fileName:入力ファイル名. (2) eol:出力する改行コード文字列 (end of line). (3) out:出力ファイルへのストリーム.テキストモードでもバイナリモー ドでもよいが,eol はそれに合わせること. 戻り値:成功ならば OK,エラーならば FAIL. 注意 :(1) ファイルの最終行が改行なしで終わっている場合,この関数は eol を 付加しない. (2) DOS や CP/M テキストファイルの EOF 文字 (CTRL+Z=0x1A) は,EOF とは見なさずそのまま出力する.(今でも使われてるの?) ・参考:EOF (通信用語の基礎知識) 2007/02/18(土) 作成 (原型) 2007/07/05(木) 文字コードの違いによる移植性の問題を修正. 2007/07/29(日) 改行コードの種類別出現回数を数えるようにした. 2007/10/20(土) ファイルの最終行が改行なしの場合の注意書きを追加. 2007/10/31(水) ・エラーメッセージを表示するようにした. ・DOS や CP/M テキストファイルの EOF 文字に関する注意を追記. ─────────────────────────────────────*/ int ConvertEol(const char *fileName, const char *eol, FILE *out) { // 改行コードの種類別出現回数 unsigned long cr_count = 0; // CR の出現回数 unsigned long crlf_count = 0; // CRLF の出現回数 unsigned long lf_count = 0; // LF の出現回数 FILE *in; int c; // OS 依存性をなくすため,入力ファイルをバイナリモードでオープンする. // (UNIX では事実上バイナリモードのみ.) if((in = fopen(fileName, "rb")) == NULL) { fprintf(stderr, "%s: Can't open \"%s\" (%s)\n", __FUNCTION__, fileName, strerror(errno)); goto Error; } while((c = fgetc(in)) != EOF) { // 注意:以下の CR,LF を '\r','\n' と書いてしまうと, // 例えばソース文字コードが ASCII 系でテキストファイルの // 文字コードが EBCDIC 系 (あるいはその逆) の場合に問題を生じる. switch(c) { case CR: if((c = fgetc(in)) == LF) { // CRLF が現れた場合 crlf_count++; } else { // 単独の (つまり LF が後続しない) CR が現れた場合: // c を in に戻す.c=EOF の場合もあるが,ungetc() // が無視するので問題ない. ungetc(c, in); cr_count++; } goto EndOfLine; case LF: // 単独の (つまり CR が先行しない) LF が現れた場合 lf_count++; // FALLTHROUGH EndOfLine: // 行末に達した場合:改行コードを出力する. if(fputs(eol, out) == EOF) goto WriteError; break; default: // 改行コード以外:そのまま出力する. if(fputc(c, out) == EOF) goto WriteError; } } fclose(in); // 改行コードの種類別に出現回数を出力する. printf("改行コードの出現回数:CR=%lu CRLF=%lu LF=%lu\n", cr_count, crlf_count, lf_count); return OK; WriteError: fprintf(stderr, "%s: File write error (%s)\n", __FUNCTION__, strerror(errno)); Error : if(in != NULL) fclose(in); return FAIL; }
イマドキの CPU では,
ビッグエンディアンとリトルエンディアン以外はありえないと思いますが,
ここでは シャレで 完全を期すため,
理論的に可能なすべての4バイト・エンディアンを扱っています.(笑)
CHAR_BIT≧10 の場合にバグあり.気が向いたら修正予定.
#include <assert.h> #include <limits.h> /* for CHAR_BIT */ #ifdef __unix__ #include <sys/types.h> #else /* __unix__ */ typedef unsigned uint32_t; /* 4バイト無符号整数 (CHAR_BIT≠8 でも同じ型名?) */ #endif /* __unix__ */ typedef int Bool; /*───────────────────────────────────── 4バイト・エンディアンを表す定数.(ここではエンディアン・コードと呼ぶ.) 「"NUXI" と "IXUN" って何?」という方はこちらへ. 2006/12/28(木) 作成 2007/03/04(日) CHAR_BIT≠8 の場合にも対応 (笑) ─────────────────────────────────────*/ #define ENDIAN_CODE(byte0, byte1, byte2, byte3) \ ((uint32_t)(((((((byte0) << CHAR_BIT) | (byte1)) \ << CHAR_BIT) | (byte2)) << CHAR_BIT) | (byte3))) #define BIG_ENDIAN ENDIAN_CODE(1, 2, 3, 4) #define LITTLE_ENDIAN ENDIAN_CODE(4, 3, 2, 1) #define NUXI_ENDIAN ENDIAN_CODE(2, 1, 4, 3) #define IXUN_ENDIAN ENDIAN_CODE(3, 4, 1, 2) /*───────────────────────────────────── 上記以外で,理論的に存在しうる4バイト・エンディアン (これで全部). しかし,実在するという話は聞いたことがない.(笑) 2007/04/01(日) 作成 ─────────────────────────────────────*/ #define UNXI_ENDIAN ENDIAN_CODE(1, 2, 4, 3) #define UINX_ENDIAN ENDIAN_CODE(1, 3, 2, 4) #define UIXN_ENDIAN ENDIAN_CODE(1, 3, 4, 2) #define UXNI_ENDIAN ENDIAN_CODE(1, 4, 2, 3) #define UXIN_ENDIAN ENDIAN_CODE(1, 4, 3, 2) #define NUIX_ENDIAN ENDIAN_CODE(2, 1, 3, 4) #define NIUX_ENDIAN ENDIAN_CODE(2, 3, 1, 4) #define NIXU_ENDIAN ENDIAN_CODE(2, 3, 4, 1) // 実在しました!! (゚д゚ ) #define NXUI_ENDIAN ENDIAN_CODE(2, 4, 1, 3) #define NXIU_ENDIAN ENDIAN_CODE(2, 4, 3, 1) #define IUNX_ENDIAN ENDIAN_CODE(3, 1, 2, 4) #define IUXN_ENDIAN ENDIAN_CODE(3, 1, 4, 2) #define INUX_ENDIAN ENDIAN_CODE(3, 2, 1, 4) #define INXU_ENDIAN ENDIAN_CODE(3, 2, 4, 1) #define IXNU_ENDIAN ENDIAN_CODE(3, 4, 2, 1) #define XUNI_ENDIAN ENDIAN_CODE(4, 1, 2, 3) #define XUIN_ENDIAN ENDIAN_CODE(4, 1, 3, 2) #define XNUI_ENDIAN ENDIAN_CODE(4, 2, 1, 3) #define XNIU_ENDIAN ENDIAN_CODE(4, 2, 3, 1) #define XIUN_ENDIAN ENDIAN_CODE(4, 3, 1, 2) /*───────────────────────────────────── 機能 :現在実行中の CPU の4バイト・エンディアンを取得する. バイエンディアンの CPU は使ったことないけど,現在設定されているエン ディアンを返すはず. 戻り値:4バイト・エンディアンを表すエンディアン・コード (xxxx_ENDIAN). これはバイト列 (下位アドレスから) 0x01,0x02,0x03,0x04 に対応する 値である. 2006/12/28(木) 作成 ─────────────────────────────────────*/ uint32_t GetEndian4(void) { uint32_t quadByte; unsigned char * const byte = (unsigned char*)&quadByte; unsigned i; for(i = 0; i < sizeof(quadByte); i++) byte[i] = (unsigned char)(i + 1); return quadByte; } /*───────────────────────────────────── 戻り値:Big Endian のときそのときに限り真. 2006/12/28(木) 作成 (関数) 2007/04/01(日) マクロに変更. ─────────────────────────────────────*/ #define IsBigEndian() (GetEndian4() == BIG_ENDIAN) /*───────────────────────────────────── 戻り値:Little Endian のときそのときに限り真. 2006/12/28(木) 作成 (関数) 2007/04/01(日) マクロに変更. ─────────────────────────────────────*/ #define IsLittleEndian() (GetEndian4() == LITTLE_ENDIAN) /*───────────────────────────────────── 使用例 ─────────────────────────────────────*/ int main(void) { uint32_t endian = GetEndian4(); // 念のため uint32_t が4バイト整数であることを確認する. assert(sizeof(uint32_t) == 4); #if 1 // ほとんどの場合はこちらで十分のはず. printf("%s Endian (0x%08lX)\n", (endian == BIG_ENDIAN) ? "Big" : (endian == LITTLE_ENDIAN) ? "Little" : "Unknown", endian); #else /* 0/1 */ // 上のコードで "Unknown" が表示されるという,貴重な経験をした方はこちらをどうぞ. printf("%s Endian (0x%08lX)\n", EndianName4(endian), endian); #endif /* 0/1 */ return EXIT_SUCCESS; }
「エンディアン 変換」で検索してくる人がたくさんいるのでオマケ.
(big ⇔ little の変換ならばバイト逆順にするだけなんで簡単なことなんですが….
おっと,それを言っちゃあ,このページには簡単なことしか書いてない….(苦笑))
CHAR_BIT≧10 の場合にバグあり.気が向いたら修正予定.
/*───────────────────────────────────── 機能 :endianCode に対応する4バイト・エンディアン名を取得する. 理論上可能なあらゆる4バイト・エンディアンに対応している.(笑) CHAR_BIT≠8 の場合にも対応している.(笑) 入力 :endianCode:エンディアン・コード. 戻り値:エンディアン名文字列.この文字列は,次回の EndianName4() の呼び出し の際に書き替えられる場合がある.endianCode が不正な場合には NULL を 返す. 注意 :(1) この関数はスレッドセーフではない.(笑) (2) この関数は (UNIX 環境以外でも) 正しく動作するはずであるが,ほと んどシャレで作った関数なので,実用的価値については疑問がある.(笑) 2007/03/04(日) 作成 ─────────────────────────────────────*/ #ifdef unix #undef unix #endif /* unix */ const char *EndianName4(uint32_t endianCode) { static const char unix[4] = { 'U', 'N', 'I', 'X' }; static char name[sizeof(uint32_t) + 1]; char usedFlag[sizeof(uint32_t)]; unsigned byte, i; switch(endianCode) { case BIG_ENDIAN : return "Big"; case LITTLE_ENDIAN: return "Little"; default: memset(usedFlag, FALSE, sizeof(usedFlag)); name[sizeof(uint32_t)] = '\0'; i = sizeof(uint32_t); do { byte = (unsigned)(endianCode & BYTEMASK(0, uint32_t)); endianCode >>= CHAR_BIT; if((byte < 1) || (sizeof(uint32_t) < byte) || usedFlag[--byte]) return NULL; // endianCode が不正. usedFlag[byte] = TRUE; name[--i] = unix[byte]; } while(i > 0); return name; } }
そのうち別ページに独立させる予定ですが,とりあえずここに入れておきます.
2007/11/19(月) 追記ここに挙げている関数は,グレゴリオ暦でもユリウス暦でも使用可能. 両者で処理が異なる場合はどちらの暦か julian フラグで指定するようにしている. これはユリウス暦用の処理がグレゴリオ暦用の一部として書けることが多いので, コードの重複を省いて保守性の向上と実行コードサイズの削減を図るため (同じようなコードを2回以上書きたくない).
ところでグレゴリオ暦の閏年の100年, 400年ルールを知らない人が少なからずいることは知っていたが, 逆に400年より長いルールがあると思っている人もいることを知ってちょっとビックリ. 確かにグレゴリオ暦に3,200年と80,000年の閏年ルールを入れると, 計算上は1年が平均 365.2422 日となって平均太陽年にかなり近くなるが, 現在のところそういうルールはないし, 将来入るという話も聞いたことがない.
参考#include <stdlib.h> /*───────────────────────────────────── 機能 :閏年か否かを判定する (グレゴリオ暦/ユリウス暦). 入力 :(1) year:年.year≦0 でも可. (2) julian:ユリウス暦ならば真,グレゴリオ暦ならば偽. 戻り値:year 年がグレゴリオ暦/ユリウス暦の閏年のときそのときに限り真. 参考 :除算回数を極力減らして高速化を図っている. ・ユリウス暦の場合:除算を全く使用しない. ・グレゴリオ暦の場合:4の倍数年のときに限り除算を1回だけ行う. したがって平均除算回数は 1/4 回. 2007/08/19(日) 作成 2008/12/24(水) div() の使用をやめ,unsigned で除算を行うようにした. ─────────────────────────────────────*/ Bool IsLeapYear(int year, Bool julian) { unsigned quot, rem; // y ← abs(year) const unsigned y = (unsigned)((year >= 0) ? year : -year); // 絶対値を取る理由: // (1) 負数の除算結果は処理系依存となるので,これを避けるため. // (また処理系によっては,符号付より無符号の整数除算の方が // わずかに速くなる可能性がある.) // (2) 4の倍数判定を除算ではなくビット演算で高速に行うため, // 万一 year が1の補数形式の負数だとまずい. if((y & 3U) != 0) return FALSE; // year が4の倍数ではない. // year が4の倍数の場合 if(julian) return TRUE; // 除算は1回ですませる.次のうちの一つを選択. // (参考:割り算は遅い (HPC SYSTEMS Inc.) #if 1 // コンパイラが賢ければ,商と剰余を1回の除算命令 // (または除算サブルーチンの呼び出し) で計算してくれるだろう. quot = y / 100U; rem = y % 100U; #elif 1 // 上の方法がうまく最適化されない (除算を2回行ってしまう) 場合 // (除算1回,乗算1回) quot = y / 100U; rem = y - quot * 100U; #else // コンパイラがアホやから自力で最適化する場合 // ・x86 の場合:DIV 命令を使えば1回の除算で商と剰余が同時に求められる. asm { : : }; #endif if(rem != 0) return TRUE; // year が100の倍数ではない. // year が100の倍数の場合 return (quot & 3U) == 0; // year が400の倍数 ⇔ 真 }
/*───────────────────────────────────── 機能 :日付が有効か否かを判定する (グレゴリオ暦/ユリウス暦). 入力 :(1) (year, month, day):日付.year≦0 でも可. (2) julian:ユリウス暦ならば真,グレゴリオ暦ならば偽. 戻り値:有効な日付のときそのときに限り真. 2007/09/17(月) 作成 2007/10/10(水) 改定 (ValueInRangeInclusive() を採用.) ─────────────────────────────────────*/ Bool IsValidDate(int year, unsigned month, unsigned day, Bool julian) { // 1〜12月の日数 (平年). static const unsigned char daysInMonth[12] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; return ValueInRangeInclusive(1, month, 12) && (1 <= day) && ((day <= daysInMonth[month - 1]) || // 計算コストの低い順,偽になる確率が高い順に条件を並べる方がよい. ((day == 29) && (month == 2) && IsLeapYear(year, julian))); }
/*───────────────────────────────────── 機能 :ある月のn回目のdow曜日の日付を求める. 入力 :(1) n:1〜5. (2) dow:曜日番号.(0:日曜,…,6:土曜). (3) dow1:その月の1日の曜日番号.1日ではなくday日の曜日がわかって いる場合には FirstDayOfWeek() を併用すればよい. 戻り値:その月のn回目の dow 曜日の日付. 参考 :曜日番号は一応,日〜土を0〜6としたが,この関数に限り1〜7でもかま わないし,何曜日から始めてもよい.(7日間にわたって1ずつ増える数列で あれば何でもよい.) 例 :2007年3月:1日は木曜(4)で,第3金曜(5)は16日. NthDayOfWeekToDay(3, 5, 4) == 16. 1990/05/04(金) 作成 (自作 Calendar ライブラリ版) 2007/03/21(水) 作成 (Web 公開版) 2007/09/17(月) すべての引数の型を int から unsigned に変更. 2008/12/24(水) 乗算の遅い処理系で乗算を使わないように変更. ─────────────────────────────────────*/ unsigned NthDayOfWeekToDay(unsigned n, unsigned dow, unsigned dow1) { unsigned day; // day ← (最初の dow 曜日の日付)−1 if(dow < dow1) dow += 7; day = dow - dow1; // day ← n回目の dow 曜日の日付 (day + 7 * (n - 1) + 1) #if 乗算の速い処理系 day += 7 * n - 6; #else // 乗算の遅い処理系 day += (n << 3) - n - 6; #endif return day; }
/*───────────────────────────────────── 機能 :○月day日 (w曜日) が,その月の何回目のw曜日か (週番号) を返す. 戻り値:○月day日の週番号 (1〜5). 2007/03/21(水) 作成 2007/09/17(月) 引数および戻り値の型を int から unsigned に変更. 2008/10/07(火) 改名 (DayToNthDayOfWeek() → DayToWeekNumber()) ─────────────────────────────────────*/ unsigned DayToWeekNumber(unsigned day) { return (day - 1) / 7 + 1; }
/*───────────────────────────────────── 機能 :ある月の day0 日が dow0 曜日のとき,同月の別の日 day の曜日を求める. 入力 :(1) day,day0:同じ月の日付. (2) dow0:day0 の曜日番号 (0〜6,何曜日から始めてもよい). 戻り値:day 日の曜日番号. 解説 :戻り値を dow とすると数学的には dow = (dow0 + (day - day0)) % 7. しかしC言語では,被除数が負になるとまずいので,それを防ぐために7の 倍数を加算して被除数が負にならないようにする.そのような倍数の最小値 である35を採用する. 2007/08/19(日) 作成 (day=1 専用関数 FirstDayOfWeek()) 2007/09/17(月) 任意の日の曜日を計算できるように一般化,AnotherDayOfWeek に 改名.FirstDayOfWeek() はマクロ化.(どちらも舌足らずでイマイチ な名前だが,あまり長くなるのも困る….) 2007/10/28(日) 説明の改訂 (変更漏れ修正を含む). ─────────────────────────────────────*/ unsigned AnotherDayOfWeek(unsigned day, unsigned day0, unsigned dow0) { return (dow0 + 35 + day - day0) % 7; } // 同じ月の1日の曜日番号を返す. #define FirstDayOfWeek(day0, dow0) AnotherDayOfWeek(1, (day0), (dow0))
はてな人力検索の質問に対する回答 #5. 日時 (年月日,曜,時) とその年の夏時間の規則が指定されたとき, OS やライブラリの時刻関数を全く使わずにその日時が夏時間か否かを判定する方法.
ダウンロード (マクロ集 ListMacro.h + サンプルプログラム (ソース,Windows 用実行ファイル,実行結果))
●データ構造┏━━━┯━┓ ┃last │・╂─────────────────────┐ ┠───┼─┨ ┏━━━━┯━┓ ┏━━━━┯━┓│ ┏━━━━┯━┓ ┃first │・╂──→┨forward │・╂─→┨forward │・╂┴→┨forward │/┃ ┗━━━┷━┛ ┠─┬──┴─┨ ┠─┬──┴─┨ ┠─┬──┴─┨ リストヘッダ ┃/│backward┠←─╂・│backward┠←─╂・│backward┃ ┗━┷━━━━┛ ┗━┷━━━━┛ ┗━┷━━━━┛ 要素 #1 要素 #2 要素 #N●特長・使い方
1980年代後半には,市販の MS-DOS 用のCコンパイラは ANSI 準拠になっていたと思います.しかしそれから10年以上経っても, UNIX ワークステーションを買うともれなく付いてくるCコンパイラは K&R 準拠のままでした.(今はどうなんだろう?)
以下のマクロは,MS-DOS 用 (ANSI 準拠) と UNIX 用 (K&R 準拠) 両方のCコンパイラで同じソースをコンパイルできるようにするために使っていたものです.
ANSI C では,関数や関数ポインタの型を宣言するには, 次のようにプロトタイプ宣言を使うのはご承知のとおり.
/* ANSI */ void MyFunc(char *string, int number); int (*MyFuncPointer)(double x, double y);
しかし K&R ではプロトタイプ宣言を使うことはできず, 引数リストの部分は次のように空でないといけない.
/* K&R */ void MyFunc(); int (*MyFuncPointer)(); /* 注意:本来の K&R には void 型はないが,当時の処理系では使えた.*/
これらを ANSI,K&R 両方でコンパイルできるようにするため, 次のように書くのは二度手間だしブサイク.
#ifdef __STDC__ /* ANSI */ void MyFunc(char *string, int number); int (*MyFuncPointer)(double x, double y); #else /* __STDC__ */ /* K&R */ void MyFunc(); int (*MyFuncPointer)(); #endif /* __STDC__ */
そこで次のように書いていた.
#ifdef __STDC__ /* ANSI */ #define PROTOTYPE(rettype_fname, args) rettype_fname args #else /* __STDC__ */ /* K&R */ #define PROTOTYPE(rettype_fname, args) rettype_fname() #endif /* __STDC__ */ PROTOTYPE(void MyFunc, (char *string, int number)); PROTOTYPE(int (*MyFuncPointer), (double x, double y)); /* 関数定義本体の引数は K&R スタイルで書く.*/ void MyFunc(string, number) char *string; int number; { : : } /* 引数に関数ポインタがある場合はこんな具合 */ PROTOTYPE(void MyFunc2, (int (*funcPtr)(double x))); void MyFunc2(funcPtr) PROTOTYPE(int (*funcPtr), (double x)); { : : }
__STDC__ は ANSI 準拠の処理系なら自動的に定義される … かと思いきや, 処理系固有の拡張機能を OFF にしないと定義されなかった. それは困るので,実際には __STDC__ じゃなくて NOPROTOTYPE のようなシンボルを 自分で定義して使っていた.
PROTOTYPE マクロの原型 (prototype?) は,確か1980年代後半に SunOS の /usr/include の中で見つけたものだったと思う. K&R の処理系がまだあるのかどうか知らないが, 古いオープンソースなどには同様のマクロが残っている可能性がある. 2000年頃,入社数年目の若手が何やらソースを眺めて首をひねっていた. 肩越しに覗いてみると,PROTOTYPE と同様のマクロだったので解説したことを覚えている.
時々検索して来る人がいるけど,一体何を知りたいのかよくわからない.
知りたいのは ASCII じゃなくて Unicode? シフトJIS? EUC-JP? ISO-2022-JP (いわゆる JIS)?
⇒ どれもこれも ASCII の変種や拡張だから同じ.
(UTF-16 と UTF-32 は符号単位のバイト数が違うけど整数値は同じ.)
ただし「CRLF」の機能を持つ単一の ASCII 系制御文字なら存在する.
規格では,LF に CR の機能を含めてもよいことになっている.
つまり LF だけで CRLF と同じ動作をさせることができる.
(その場合,「LF とは呼ばずに NL (New Line)
と呼ぶ」と昔の規格書で読んだ記憶があるが,最近の規格書では "NL"
という略語は消えている?)
猫でも知ってるとおり,UNIX 系 OS の改行コードは LF (NL), DOS/Windows の改行コードは CR LF.
■参考
C/C++ のマクロがどういうものか全くわかっていないとしか思えない検索ワードが多すぎるので追加.
C/C++ のマクロとは,「コンパイルする前に,
ソースコードの一部を置き換えるための規則」だということは理解してますか?
たとえば,
#include <stdio.h> #include <stdlib.h> #define ONE 1 #define ADD_ONE(x) ((x) + ONE) int add_one(int x) { return x + 1; } int main(void) { printf("ADD_ONE(100) = %d\n", ADD_ONE(100)); printf("add_one(100) = %d\n", add_one(100)); return EXIT_SUCCESS; // stdlib.h で定義されているマクロ }
というソースコードは,プリプロセッサが次のように置き替えた後でコンパイルされる.この置き換えを マクロ展開 (macro expansion) という.
int main(void) { printf("ADD_ONE(100) = %d\n", ((100) + 1)); printf("add_one(100) = %d\n", add_one(100)); return 0; }
つまりプリプロセッサは,ONE や ADD_ONE( ) などのマクロが呼び出された場所に, それらの定義の中身をぶちまけるのである.プリプロセスが終わると ONE や ADD_ONE() の定義は不要になるので, コンパイルが始まった時点でマクロというものはもはや存在していない.
なおプリプロセス後のファイル (*.i) は,次のようにすれば見ることができる.
この検索ワードも以前から多い.たぶん「関数では値を返すために return
を使うのに,なぜ関数マクロでは使用しないのか?」
という疑問なんだろうと思っていたら,今日「C return
で値を返すマクロ」で検索して来た人がいたので,
「ああ,やっぱりそうか」と思った.
マクロが関数と同じようなものだと誤解している初心者が多いんだろう.
(マクロをちゃんと解説していない入門書や授業が多いのか?)
上のマクロ展開の説明を読めばわかると思うのでこれ以上の説明は不要だろうけど, そのサンプルコードで,マクロ ADD_ONE(x) の定義を関数 add_one( ) と同じように (下記) 変えたらどうなると思う?
#define ADD_ONE(x) return ((X) + ONE)
理解できない人は,マクロ展開後に main() がどうなったか,*.i ファイルを見て確認すること.
上に書いたとおり C/C++
のマクロはコンパイル前にソースコードの一部を置き替えるための規則だけど,
「動的」というのはプログラムの実行時
(もはやソースコードが変更されても影響を受けない)
に何かをするということだから,
「C/C++ の動的マクロ」というのは名辞矛盾
(概念上ありえない).
(余談:インタプリタ言語である Lisp のマクロは動的.コンパイラのある
Lisp 処理系ならば静的にも使える.)
もしかしたら for 文や if 文などに展開されるマクロを知りたいのかもしれないけど,
一応念のために言っとく.
for,if,new などはマクロじゃなくて予約語 (コンパイラにとって特別な意味を持つ単語) ね.
#if,#ifdef,#include など,行頭の '#' から始まるのはプリプロセッサ命令 (または指令,指示,ディレクティブ).
マクロを定義する #define もプリプロセッサ命令の一つ.
# も ## もマクロの中で使うものだから検索ワードとしてはおかしくないけど,
これも一応念のために言っとく.
# はマクロじゃなくて文字列化演算子
(stringizing operator),##
はトークン連結演算子 (token concatenation operator) ね.
■余談 (昔話)
# と ## は ANSI C で導入されたが,それ以前の多くの処理系では次のようなトリックで実現していた.
// トークンの文字列化 #ifdef __STDC__ // ANSI C #define STRINGIZE(token) #token #else /* __STDC__ */ // ANSI 以前:文字列内でもマクロ置換される処理系が多かった. #define STRINGIZE(token) "token" #endif /* __STDC__ */ // トークン連結 #ifdef __STDC__ // ANSI C #define CONCATENATE(token1, token2) token1##token2 #else /* __STDC__ */ // ANSI 以前:コメントを単に削除する (空文字列に置き換える) 処理系が多かった. // ANSI C はコメントを単一のスペースに置き換えるのでこの方法は使えない. // 例:CONCATENATE(xyz, 123) → xyz/**/123 → xyz123 #define CONCATENATE(token1, token2) token1/**/token2 #endif /* __STDC__ */
これは間違いなく勘違いしている!
L,U,UL
などはマクロじゃなくて整数リテラルのサフィックスね.
L だけはワイド文字(列)リテラル L"…" にも使う (この場合は先頭に付く).
参考
? : はマクロじゃなくて三項演算子.Cの入門書って読んだことある?
なんか噂によると,三項演算子を知らない「Cプログラマ
(自称)」が結構多いみたいだけど….
そもそも '?' も ':' もマクロ名に使える文字じゃないでしょ!
どうしてこれをマクロだと思ったの? (謎)
他にも構文や演算子のことをマクロだと勘違いしているらしい検索ワード多いし….
(上に書いた for,if,new もそうかもしれない.)
なぜ構造体の配列だけ特別だと思うの?
なんか以前から,象の卵 (あるはずのないもの) を探しに来る人が結構いる.
象の卵をいつまでも探し続ける人達(´・ω・)カワイソス….
というわけで追記.
時々「C言語 構造体 要素数(メンバ数)」等で検索して来る人がいるけど, Cでは構造体のメンバ数を取得することはできない. 仮に構造体のメンバ数だけを取得できたとしても, メンバの番号を指定してその型, サイズ,オフセットなどを取得できなければ何の役にも立たないのに, 一体何に使うつもりですか? (激謎)
Cのプログラムで構造体定義データを使いたければ, (二度手間だけどCの構造体宣言とは別に) 自分で用意するしかない. 「それはしたくないけど,どうしてもプログラム中で構造体定義データを使いたい」 という人は, リフレクション機能のあるプログラム言語を使用してください.
2011/11/27(日) 追記
これを読んでもまだ構造体のメンバ数の取得方法を探しまわっている人が何人もいるようだ. 「Cの構造体メンバ数の取得方法があるはずだ」と信じている人は, Cプログラムをコンパイルしてできた実行ファイルにも構造体というものが実在していると思い込んでいるんだろう. 実際には,構造体に含まれていたメンバ (基本データ型) が,相対的位置関係を保って順番にメモリ上に並んでいるだけだ (まあ,Cではそれを「構造体」と呼ぶわけだが).
CPU が理解 (直接処理) できるのは,それがサポートしている基本データ型 (整数型および浮動小数型) だけであって,構造体を扱う命令というのは (Java チップなど特殊なプロセッサ以外には) 存在しない.
したがって,メモリ上のどこからどこまでが一つの構造体だとか, メンバ数やメンバ名などという情報は,コンパイルが終わったら全部捨てられてしまう. 実行ファイルに構造体というものが存在しないのだから, 当然メンバ数だのメンバ名だのを取得する方法も存在しない. そうしたければ自分で明示的にそのデータを別途用意するしかない.
そもそもCは「面倒見のよい便利な言語」ではなく, 「高級アセンブラ」である. ハードウェアをプログラマが完全に制御できるようにするための言語がアセンブラだが, さすがにすべてのプログラムをアセンブラで書くのは大変な労力だし, 移植性もないのでCを使うのである. だからCは,メモリ上のデータを1バイト, 1ビット単位でプログラマの思い通りに操作できなければならない. Cのプログラマが構造体を使うのはメモリ上にメンバが順番に並んでほしいからであって, 頼んでもいないメンバ数やメンバ名などをコンパイラが勝手に追加すると困ることがある. (特にメモリがわずかしかない組み込み機器など)
「構造体 エンディアン」とか「共用体 エンディアン」で検索してくる人が時々いるけど,
そんなものはありません.
「エンディアン」がどういうものか理解してますか?
(「共用体を使ってエンディアン変換したい」ということならアリだけど.)
「エンディアン」を一般的に定義すると,
複数の要素 (普通はバイトまたはビット) からなるデータを表現するのに, 要素を並べる順序に自由度がある場合, 順序の選択肢の一つ一つがエンディアンである.といえると思う.余談だが,日付の表記方法を次のように呼ぶプログラマもいるらしい.
Cの構造体のメンバの順序は (非標準の処理系依存機能を使わない限り) ソースに記述された順序と一致するので,エンディアンの違い (つまり要素順序の自由度) などどいうものはない. また共用体では,同時に存在するメンバは高々一つだけなので, そもそもメンバの (空間的) 順序というものがない.
普通「構造体のエンディアン変換」という場合, 「構造体のエンディアン」(そんなものないってば!) を変換するという意味ではなく,構造体に含まれる基本データ型 ((複数バイト長の) 整数型,浮動小数型) のうち必要なものをそれぞれエンディアン変換するという意味. それに変換元や変換先の全メンバのエンディアンが同じとは限らない. 共用体の場合も同様だが,変換する時点でどのメンバが存在しているのかを判定し, そのメンバだけを変換する必要がある.
「C++ 動的配列 要素数」などで検索して来る人も時々いる.
new[ ] で確保した配列の要素数を取得したいということだろう.
結論を先に書くと,C++ の規格ではそういう方法は存在しないだけでなく,
処理系依存の方法や裏技さえ存在しない処理系もある.
(C++ はあまり使ってないし,規格書も持っていないので間違っているかもしれない.)
最初は「どうせ配列の直前の数バイト (処理系依存) に要素数が入っているだろうから, それを読み出すだけで OK」と簡単に考えていた.
class A { : : }; A *array = new A[NELEMENTS]; // 多くの32ビット処理系では,K はたぶん -1 か -2. // (デバッグや最適化オプションの指定によっても変わる可能性がある.) const size_t nElements = ((const size_t*)array)[K];
↓このページによると,g++ (バージョン不明) では配列の直前の4バイト (=sizeof(size_t)) に要素数が入っているから,上のコードで K=-1 とすれば取得できるはず.
ただしデストラクタを呼ぶ必要がない場合, その4バイトは存在しない (と上記のページに書いてある). なるほど,無駄なメモリを使わないための最適化ってことね. つまり処理系が (実行時の) 配列要素数を管理していないので, 上のコードは使えないし,当然別の取得方法も存在しない.
g++ 以外の処理系でも上記のような最適化を行っている可能性がある. 最適化やデバッグオプションの指定や配列要素のアラインメントによっても, 「配列直前の数バイト」のサイズと構造が変わる可能性がある.
逆に言うと,配列の要素数を取得する手段を提供する処理系があるとすれば, 配列のメモリサイズが最適化されていない (不要な場合でもヘッダに要素数を持っている) ということになる. (特に汎用) メモリ管理ライブラリの開発者が, どれだけ高速化と省メモリを両立させるために知恵を絞っているか, 想像すらしたことないでしょ?(笑)
できません.どうしても知りたければ,(コンパイラではなく) リンカのオプションでマップファイル (*.map) を作成するように指定し,それを見て確認してください.
ただし,
inline 関数は,その呼び出し元の関数の中に展開されて (=溶けて)
しまうので,コンパイル後に「inline 関数」というものは独立して存在しない
(当然,マップファイルに出てこない).
だから inline 関数では,そのサイズという概念自体意味がない.
(「食塩水の中の食塩の体積 (質量ではない)」に意味があると思う?)
inline と指定していなくても,最適化により勝手に inline 化されてしまう場合がある.
例えば gcc の場合,inline 指定は単なるヒント程度の扱いであり,
inline を指定しても inline 化されない場合や,その逆の場合が存在する.
1ヶ所からしか呼ばれない static 関数は,(inline
化のデメリットがほとんどないため)
inline と指定しなくても inline 化されるようである.
(マップファイルに出てこないことで確認した.ただし常にそうなるかは不明.)
個々の関数の範囲を超えた最適化を行う (Visual C++ では「プログラム全体の最適化 (/GL)」と,リンカオプション「リンク時のコード生成 (/LTCG)」を指定する) 場合,リンカによる最適化で関数のサイズが変わったり, 複数の関数が一つにまとめられたり,消滅したりする場合がある.
実例:つい先日,同僚がデバッグ中にデバッガの動きがおかしいと言い出した. ソースコードには2つの非常によく似た関数 FuncA1( ) と FuncA2( ) があり,両者の違いはローカル変数名と引数の型だけだった. デバッガで動きを確認すると,FuncA2( ) が呼び出されるはずの場所で FuncA1( ) が呼び出されていた.(にもかかわらず,ローカル変数名は FuncA2( ) のものが表示されていた.)
実行ファイルを逆アセンブルして調べてみると,FuncA2( ) が存在せず, FuncA2( ) への呼び出しはすべて FuncA1( ) に変更されていた. どうやら最終的に FuncA1( ) と FuncA2( ) のバイナリが同等になるため,/LTCG による全体最適化で FuncA2( ) が FuncA1( ) に置き換えられて消滅したらしい. デバッガが矛盾した表示をしていたのは, この最適化がデバッグ情報に正しく反映されていないためだろう.
このように,最終的に関数のサイズ (どころか存続) を決定するのはリンカであってコンパイラではない.だからコンパイラ (コンパイル時) に関数の (最終的な) サイズがわかるわけがない.
|
珠玉のプログラミング―本質を見抜いたアルゴリズムとデータ構造 posted with amazlet at 10.06.20 ジョン ベントリー ピアソンエデュケーション 売り上げランキング: 7857 おすすめ度の平均: アルゴリズムについて勉強したい人には必読の本です楽しく読めるプログラミングの本 「プログラミング」と言う作業を見つめなおすのに最適。「設計する」と言う概念がよく分からない初級プログラマにも 納得!アルゴリズムは重要 プログラマなら読むべき本 |
|
ブライアン カーニハン ロブ パイク アスキー 売り上げランキング: 20088 おすすめ度の平均: 良著です。入門書の次の次の次くらいに! 繰り返し読む必要あり 絶対にお勧めの本です 良いプログラマになりたいあなたに |
|
アルゴリズムクイックリファレンス posted with amazlet at 10.12.31 George T. Heineman Gary Pollice Stanley Selkow オライリージャパン 売り上げランキング: 28776 |
|
共立出版 売り上げランキング: 319557 おすすめ度の平均: 基本的なアルゴリズムを収集 |
ハッカーのたのしみ―本物のプログラマはいかにして問題を解くか posted with amazlet at 10.06.12 ジュニア,ヘンリー・S. ウォーレン エスアイビーアクセス 売り上げランキング: 27265 おすすめ度の平均: ビットの楽しみたのしみ? たしなみ? ちゃんと読むと得した気分になれます 最後の頑張りに効きます Hackっていうのは、こういうコトさ |
著者ページ:Hacker's Delight
第2版 (英語版) 第2章が無料で読める.
主に2進整数やビットパターンのさまざまな演算技法について解説している. 基本的には (特定のプログラミング言語に依存しない) 数学的な解説が中心だが,C言語によるサンプルコードも示している.
アラインメントやオフセットの計算に使える「2の冪乗の倍数への切り上げ/切り下げ」や, メモリブロックの管理に使える「次の2の冪乗への切り上げ/切り下げ」, (ビット/バイト) エンディアン変換や FFT (高速フーリエ変換) で使われるビットリバース (ビット逆順) などを含む「ビットやバイト単位の並べ替え」など.
■Cのオーバーフローについて (2015/02/21(土) 追記)
Cは整数やポインタの演算オーバーフローを教えてくれないので, 必要ならば自力で判定しなければならないが,色々落とし穴が存在するらしい. (下記「Cのオーバーフローに関するリンク集」を参照. ちなみに整数・ポインタ演算オーバーフローチェックは,むしろアセンブラの方がずっと簡単. CPU の演算命令がオーバーフローフラグをセットしたり,例外を発生させたりする.)
この中の「C コンパイラの最適化の問題」の最後の方で,「ハッカーのたのしみ」 に四則演算のオーバーフローのチェック方法が記載されていると書かれている. 自分の本は現在行方不明なので内容を確認できないが,Amazon の「なか見!検索」の目次には確かに項目がある (第2章 2-12,第2版の目次では 2-13).
さらに追記:第2版 (英語版) 第2章 (pp.28-36) が無料で読める. (今見つけたばかりなのでまだ読んでない.)
●参考:Cのオーバーフローに関するリンク集
|
省メモリプログラミング―メモリ制限のあるシステムのためのソフトウェアパターン集 (Software patterns series) posted with amazlet at 10.06.12 ジェイムズ ノーブル チャールズ ウィアー ピアソンエデュケーション 売り上げランキング: 80302 おすすめ度の平均: メモリ制限のあるシステム分類が上手い 組み込み向けのデザインパターンとしてはまともです。 すべての設計者・プログラマに必須 |
「省メモリ」とあるが,メモリ管理の高速化についても参考になる技法が解説されている. 時々「malloc 高速(化)」などで検索して来る人がいるが, malloc の速度をこれ以上大きく改善する余地はあまりないと思う (あるとしても非常に難しいだろう).その理由は,
それでもなお malloc の高速化それ自体を目指したい人には「(悲愴な顔で) 頑張ってください」としか言えないが, 「アプリケーションのメモリ管理を高速化したいから高速な malloc が欲しい」というのならあまりにも芸がなさすぎる. そういう人はこの本の「第5章 Memory Allocation:メモリ割当て」を読んで反省してください.(笑)
アプリを高速化したいなら,できるだけ malloc/free を呼び出す頻度を減らすこと. そのためには1回の malloc で確保した大きな領域 (メモリプール) に多数のオブジェクトを詰め込む必要がある (これは省メモリにもなる) が, どのオブジェクトを同じ領域に入れるべきかはオブジェクトの寿命 (extent),サイズ, アラインメントなどを考慮して決める必要がある. 特に,寿命を知っているのはアプリケーションだけだ.
ところで,省メモリが高速化につながる場合も多い.昔からメモリと速度のトレードオフ (高速でメモリを大量に使用するアルゴリズム (例えばテーブル参照) を使うか, それとも低速でメモリを少ししか使用しないアルゴリズムを使うか) がよく問題になるので,省メモリと高速化は両立しないと思い込んでいる人もいるだろう. しかし最近の CPU は命令実行速度に比べてメモリアクセス速度がはるかに遅いので, 無駄なメモリを削減したり,メモリ上のデータ配置を変える (同時期に頻繁に使用するデータをなるべく少数のキャッシュラインに集中させる) と高速化されることも多い. (1970年代以前の CPU は命令実行とメモリアクセスが同期していたので同程度の速度だった.)
さて,ここで問題.次のコードで大きな2次元配列 (例えば画像データ) をコピーする場合,(1) と (2) のどちらが速いか.またその理由を述べよ. (理由を書かなければ0点)
int src[M][N], dest[M][N]; unsigned i, j; // (1) for(i = 0; i < M; i++) for(j = 0; j < N; j++) dest[i][j] = src[i][j]; // (2) for(j = 0; j < N; j++) for(i = 0; i < M; i++) dest[i][j] = src[i][j];
|
Binary Hacks ―ハッカー秘伝のテクニック100選 posted with amazlet at 10.06.12 高林 哲 鵜飼 文敏 佐藤 祐介 浜地 慎一郎 首藤 一幸 オライリー・ジャパン 売り上げランキング: 35295 おすすめ度の平均: 組み込み系の開発者は必携ですハードコア?なソフトウエア 大工さんにおける電動工具の紹介本 当然教科書ではない。でも、とても参考になります。 バイナリアンの基本 |
|
ガベージコレクションのアルゴリズムと実装 posted with amazlet at 10.06.17 中村 成洋 相川 光 秀和システム 売り上げランキング: 4312 おすすめ度の平均: 擬似コードのバグは見て見ぬふりGCの入門書として今のところ最強! |
|
リンカ・ローダ実践開発テクニック―実行ファイルを作成するために必須の技術 (COMPUTER TECHNOLOGY) posted with amazlet at 10.12.18 坂井 弘亮 CQ出版 売り上げランキング: 26556 |
|
Linkers & Loaders posted with amazlet at 10.06.17 John R. Levine オーム社 売り上げランキング: 139529 おすすめ度の平均: プログラムが実行される仕組みが良く分かる概要が書かれた本 ひどい訳 dllのしくみがわかる! パッケージソフト開発者の必読書 |
|
実践 デバッグ技法 ―GDB、DDD、Eclipseによるデバッギング posted with amazlet at 10.06.12 Norman Matloff Peter Salzman オライリージャパン 売り上げランキング: 79214 |
デバッガの理論と実装 (ASCII SOFTWARE SCIENCE Language) posted with amazlet at 10.06.17 ジョナサン・B. ローゼンバーグ アスキー 売り上げランキング: 295384 おすすめ度の平均: デバッグできないとき普通に読んでいくだけでも面白い デバッカの理論には必読 |
|
ARM組み込みソフトウェア入門―記述例で学ぶ組み込み機器設計のためのシステム開発 (Design Wave Advanceシリーズ) posted with amazlet at 12.03.17 Andrew N. Sloss Chris Wright Dominic Symes CQ出版 売り上げランキング: 213165 |
|
ARM Cortex‐M3システム開発ガイド―最新アーキテクチャの理解からソフトウェア開発までを詳解 (Design Wave Advance) posted with amazlet at 12.03.17 Joseph Yiu CQ出版 売り上げランキング: 178355 |
CQ出版社の書籍案内 (内容見本PDFあり,立ち読み可)
|
完全版 世界の定番ARMマイコン 超入門キット STM32ディスカバリ: デバッガ搭載&はんだづけ不要!Cortex-M3をホントに始められる (トライアルシリーズ) posted with amazlet at 12.03.17 島田 義人 永原 柊 CQ出版 売り上げランキング: 21953 |
|
ARM9/11/XScaleハンドブック (TECH I Processor) posted with amazlet at 12.03.17 CQ出版 売り上げランキング: 324832 |
|
ARMでOS超入門 (ARMマイコン) posted with amazlet at 12.03.17 桑野 雅彦 岡田 好一 共著 CQ出版 売り上げランキング: 108107 |
|
アセンブリ言語の教科書 posted with amazlet at 09.11.03 愛甲 健二 データハウス 売り上げランキング: 72168 おすすめ度の平均: アセンブリ言語を広範囲に解説した本冗長感は否めない、そして索引も必要 ゆとり教育の教科書 アセンブリ言語の良書 著者の熱意が伝わります |
著者サイト
|
|
x86/x64環境で開発する上で, gccとVCはどちらも非常に優れたC/C++コンパイラです. ただLinuxとWindowsのどちらの環境でも動作するようなC/C++コードを書くためには, gccとVC, およびそれらが動作するOSの違いが問題になることがあります. ここではそれらの違いについてまとめていきたいと思います.
けっこう、初心者から年季の入った人でも勘違いしている人が多いんじゃないかネタ。
学生のために書いた、C言語ポインタについての解説。かなり深いです。
中級向きのさまざまなデータ構造やアルゴリズムについて、解説したもの。 鬼のように深いです。
このページは、プログラミングや数値計算の方法についてあまり急がずに学ぼうとする人のための解説です。 読者としては、初歩的なC/C++言語の経験を仮定しています。
このサイトはインテル© アーキテクチャー・プロセッサー向けにソフトウェア開発をされている皆様向けに、 「開発に役立つ資料を掲載しよう」という目的で開設されました。
昨今話題の並列化から、新しい命令セットの活用法、プロセッサーに向けたチューニングなど、 さまざまな分野の技術資料や話題を掲載していきます。当面は隔週ごとに新しい記事を公開します。
プログラム実行時間の99%以上がループ内で消費される。 プログラムの速度が問題になる場合、ループを最適化するのは非常に重要である。
CERT C セキュアコーディングスタンダードは、 C言語を使ってセキュアコーディングを行うためのルール (Rule) とレコメンデーション (Recommendation) を定めています。目的は、 脆弱性につながる恐れのある危険なコーディング作法や未定義の動作を削減することです。 セキュアコーディングスタンダードを採用することで、 より品質の高い、堅牢で攻撃に耐えられるシステム開発が可能になるはずです。
このページの主な更新は Blog でお知らせします.
Copyright © 2007-2016 noocyte. E-mail: relipmoced (a) yahoo.co.jp (" (a) " を半角のアットマークに書き替えてください.) リンクはご自由に. 「noocyte のプログラミング研究室」トップページに戻る. |