1


2

私はRHEL 5.1 64 bitプラットフォームからgcc 4.1.2を使っています。

私は効用関数を持っています:

void str_concat(char * buff、int buffSize、...);

これは可変長リスト(…​)で渡されたchar *のいずれかを連結しますが、最後の引数はNULLにして、引数の終わりを指定します。 64ビットシステムでは、NULLは8バイトです。

今問題に。 私のアプリケーションは直接/間接に2つのstddef.hファイルを含みます。

最初のものは* / usr / include / linux / stddef.h *で、これはNULLを次のように定義します。

#undef NULL #if定義(__ cplusplus)#define NULL 0#その他#define NULL((void *)0)#endif

2番目は* / usr / lib / gcc / x86_64-redhat-linux / 4.1.2 / include / stddef.h *です。

#ifが定義されました(_STDDEF_H)||定義されている場合はdefined(__need_NULL)#undef NULL / *。 * / #ifdef __GNUG__ #define NULL __null #else / * G * / #ifndef __cplusplus #define NULL((void *)0)#else / * C * / #define NULL 0 #endif / * C * / #endif / * G * / #endif / * NULLが定義されていないか、NULLが必要です。 * / #undef __need_NULL

もちろん、NULLを__null(8バイト)と定義しているのに対し、1番目は整数0(4バイト)と定義しているので、2番目のものが必要です。

/usr/include/linux/stddef.hが誤ってインクルードされないようにするにはどうすればよいですか?

UPD:

  1. コンパイル行はかなり簡単です。g -Wall -fmessage-length = 0 -g -pthread

  2. あなたの多くは(void *)0を渡すことを勧めました。 もちろんこれでうまくいきます。 関数が多く使用されているという問題は、多くの場所を意味します。 8バイトサイズのNULL - 私は私がC規格が約束するものを与える解決策を見つけたいと思います。

5 Answer


12


この場合、「NULL定義問題」はありません。 あなたのコードでどのように `NULL`を使おうとしているのかに問題があります。

`NULL`はそれ自身でC / Cの可変関数に移植可能に渡すことはできません。 渡す前に明示的にキャストする必要があります。 あなたの場合は、引数リストの終端文字として `(const char *)NULL`を渡さなければなりません。

あなたの質問はCとしてタグ付けされています。 いずれにせよ、サイズにかかわらず、CではNULLは常に整数定数として定義されます。 CではNULLをポインタとして定義することは違法です。 あなたの関数はポインタ( const char *)を期待しているので、Cコードではそれに対する `NULL`の定義は全く機能しません。

よりきれいなコードのためには、次のようにあなた自身の定数を定義することができます。

const char* const STR_TERM = NULL;

そしてあなたの関数の呼び出しにそれを使用してください。 しかし、その目的のためだけに意味のあるNULLを使うことはできません。 プレーンなNULLが可変引数として渡されるときはいつでも、それは修正されなければならない明白な移植性のバグです。

*追加:*あなたの更新は「C標準は8バイトサイズの `NULL`を約束する」と主張している(私が推定する64ビットプラットフォームで)。 これは意味がありません。 C標準は、 `NULL`についてそのようなことを約束していません。

NULLは右辺値として使われることを意図しています。 それは特定のサイズを持たず、実際のサイズが遠く離れたところで問題になるかもしれない `NULL`の有効な使用法はありません。

'' '' '

ISO / IEC 14882:1998、セクション18.1「タイプ」、段落4からの引用

_ _ マクロNULLは、この国際標準(4.10)で定義されているC ++ nullポインター定数です。^ 180)^

^ 180)^可能な定義には0と0Lが含まれますが、(void *)0は含まれません。 _ _


5


1つの解決策 - おそらく最善だが確かに非常に信頼性が高い - は、明示的なnull charポインタを関数呼び出しに渡すことです。

str_concat(buffer, sizeof(buffer), "str1", "str2", ..., (char *)0);

or:

str_concat(buffer, sizeof(buffer), "str1", "str2", ..., (char *)NULL);

これは例えばPOSIXシステムでの `execl()`関数のための標準的なお勧めの方法です、そして全く同じ理由で - 可変長引数リストの末尾の引数は通常の昇格の対象となります(charまたはshort to int; floatしかし、それ以外の点ではタイプセーフではありません。

C言語の専門家が一般にhttps://stackoverflow.com/questions/1657883/variable-number-of-arguments-in-c [可変長引数リスト]を避けるのもこのためです。それらはタイプセーフではありません。


3


2番目のファイルの `GNUG`ケースを削除し、ifdef / endifを反転すると、両方のファイルで以下のようになります。

#undef NULL
#if defined(__cplusplus)
#define NULL 0
#else
#define NULL ((void *)0)
#endif

これは、Cコンパイルの場合はNULLを((void *)0)、Cの場合は0と定義するということです。

つまり、単純な答えは「Cとしてコンパイルしない」です。

あなたの本当の問題はあなたの可変長引数リストであなたのコンパイラの予測不可能な引数サイズと組み合わせてNULLを使いたいというあなたの欲求です。 リストを終わらせるために、NULLの代わりに "(void *)0"を書いて、コンパイラに4バイトのintの代わりに8バイトのポインタを渡すようにしてください。


1


システムインクルードはねじれた迷路のため、インクルードを修正できない場合があります。

NULLの代わりに(void *)0または(char *)0を使用して問題を解決することができます。

それを考慮した後、私は以前のNULLを再定義するという考えを拒絶しています。 これは悪いことであり、他の多くのコードをめちゃくちゃにする可能性があります。


0


私は以前以下の答えで答えました。 しかし、それから私は私がいくつかの情報を誤って解釈し、間違った答えをしたのを見た。 ちょうど好奇心から、私はVS2008で同じテストをしました、そして、私は異なる結果を得ました。 これは単なる脳の運動です…​

なぜあなたは2番目のものが必要なのですか? どちらのヘッダも同じことを言っています。 そして、あなたが0を書いてもNULLを書いても((void *)0)それらのすべてが8バイトかかるでしょう。

GCC 4.1.3を使用して64ビットプラットフォームで簡単なテストをしました。

#含める

void str_concat(char * po_buf、int pi_max、...){strcpy(po_buf、 "Malkocoglu"); / *にせ* *

int main(){char buf [100]; str_concat(buf、100、 "abc"、1234LL、 "def"、5678LL、 "ghi"、2345LL、 "jkl"、6789LL、 "mno"、3456LL、0、 "pqx"、0); 1を返します。 }

そして、これはコンパイラによって生成されたアセンブリです…​

メイン:
.LFB3:
pushq%rbp
.LCFI3:
movq%rsp、%rbp
.LCFI4:
subq $ 192、%rsp
.LCFI5:
leaq -112(%rbp)、%rdi movl $ 0、64(%rsp)0 movq $ .LC2、56(%rsp) "pqx" movl $ 0、48(%rsp)0 movq $ 3456、40(%rsp)3456LL movq $ .LC3、32(%rsp) "mno" movq $ 6789、24(%rsp)6789LL movq $ .LC4、16(%rsp) "jkl" movq $ 2345、8(%rsp)2345LL movq $ .LC5、( %rsp) "ghi" movl $ 5678、%r9d 5678LL movl $ .LC0、%r8d "def" movl $ 1234、%ecx 1234LL movl $ .LC1、%edx "abc" movl $ 100、%esi 100 movl $ 0、%eax呼び出しstr_concat movl $ 1、%eaxはretを残します

すべてのスタック変位が8バイトであることに注意してください…​

コンパイラは0を32ビットデータ型として扱います。 それはスタックポインタの正しい置き換えを行いますが、プッシュされる値は32ビットではありません!

私はVS2008で同じテストをしました、アセンブリ出力は以下の通りです:

mov QWORD PTR [rsp 112]、0がrax、オフセットフラット:$ SG3597 mov QWORD PTR [rsp 104]、rax mov QWORD PTR [rsp 96]、0 mov QWORD PTR [rsp 88]、3456。 00000d80Hリーラックス、オフセットフラット:$ SG3598 mov QWORD PTR [rsp 80]、rax mov QWORD PTR [rsp 72]、6789; 00001a85Hリーラックス、オフセットフラット:$ SG3599 mov QWORD PTR [rsp 64]、rax mov QWORD PTR [rsp 56]、2345。 00000929H lea rax、オフセットフラット:$ SG3600 mov QWORD PTR [rsp 48]、rax mov QWORD PTR [rsp 40]、5678; 0000162eHリーラックス、オフセットフラット:$ SG3601 mov QWORD PTR [rsp 32]、rax mov r9d、1234。 000004d2Hリーr8、オフセットフラット:$ SG3602 mov edx、100。 00000064Hリーrcx、QWORD PTR buf $ [rsp]電話しますか?str_concat @@ YAXPEADHZZ; str_concat

今回のコンパイラは別のコードを生成し、0を64ビットデータ型として扱います(QWORDキーワードに注意してください)。 値とスタック変位の両方が正しいです。 VSとGCCは異なる動作をします。