52

7.5につき、

[errno] は int 型を持つ変更可能な lvalue175) に展開され、その値はいくつかのライブラリ関数によって正のエラー番号に設定されます。errno がマクロであるか、外部リンケージで宣言された識別子であるかは指定されていません。実際のオブジェクトにアクセスするためにマクロ定義が抑制されている場合、またはプログラムが errno という名前の識別子を定義している場合、動作は未定義です。

175) マクロ errno は、オブジェクトの識別子である必要はありません。関数呼び出し (*errno() など) の結果、変更可能な左辺値に展開される場合があります。

&errnoこれが制約違反ではないことを要求するのに十分かどうかは、私には明らかではありません。C 言語には、演算子が制約違反となる左辺値 (register-storage-class 変数など。ただし、これらは自動にしかerrnoできないため、そのように定義することはできません) があります。&

正当な C の場合&errno、定数である必要がありますか?

4

5 に答える 5

18

したがって、§6.5.3.2p1は

単項&演算子のオペランドは、関数指定子、[]または単項*演算子の結果、またはビットフィールドではなくレジスタストレージクラス指定子で宣言されていないオブジェクトを指定する左辺値のいずれかでなければなりません。 。

これは、これら2つのカテゴリに含まれない左辺値には問題がないことを意味すると解釈できます。&lvalueそして、あなたが言ったようにerrno、レジスタストレージクラス指定子で宣言することはできません、そして私はあなたがプレーンのタイプを持つビットフィールドを持つことはできないと思います(今はチェックするための参照を追いかけていませんが)int

したがって、仕様は&(errno)合法的なCである必要があると思います。

&errnoが正当なCである場合、それは一定である必要がありますか?

私が理解しているように、マクロになることを許可するポイントの一部errno(およびそれがたとえばglibcにある理由)は、それがスレッドローカルストレージへの参照になることを許可することです。この場合、スレッド間で一定ではありません。 。そして、それが一定でなければならないと期待する理由は見当たらない。の値がerrno指定されたセマンティクスを保持している限り、プログラムの過程で異なるメモリアドレスを参照するように逆Cライブラリを変更できなかった理由はわかりません。&errnoたとえば、設定するたびにバッキングストアを解放して再割り当てしますerrno

ライブラリによって設定された最後のNerrno値のリングバッファを維持し、&errno常に最新のものを指すことを想像できます。特に便利だとは思いませんが、仕様に違反しているとは思えません。

于 2012-10-18T00:59:22.363 に答える
16

C11 仕様をまだ誰も引用していないことに驚いています。長い引用で申し訳ありませんが、関連性があると思います。

7.5 エラー

ヘッダーはいくつかのマクロを定義しています...

...と

errno

これは、型とスレッド ローカル ストレージ期間を持つ変更可能な lvalue(201) に展開されint、その値はいくつかのライブラリ関数によって正のエラー番号に設定されます。実際のオブジェクトにアクセスするためにマクロ定義が抑制されている場合、またはプログラムが という名前の識別子を定義しているerrno場合、動作は未定義です。

初期スレッドの の値はerrno、プログラムの起動時にはゼロです (他のスレッドの の初期値はerrno不定値です) が、ライブラリ関数によってゼロに設定されることはありません。(202) errno の値は、エラーの有無にかかわらず、ライブラリ関数呼び出し。ただし、 errnoこの国際規格の関数の説明で使用が文書化されていない場合に限ります。

(201) マクロerrnoは、オブジェクトの識別子である必要はありません。関数呼び出しの結果、変更可能な左辺値に展開される場合があります (たとえば、*errno())。

(202) したがって、エラー チェックに を使用するプログラムはerrno、ライブラリ関数呼び出しの前にゼロに設定し、その後のライブラリ関数呼び出しの前に検査する必要があります。もちろん、ライブラリ関数は、の値が戻る直前に の値がまだゼロであるerrno場合に元の値が復元される限り、エントリで の値を保存してゼロに設定できます。errno

「スレッドローカル」registerはアウトを意味します。タイプintは、ビットフィールドがアウトであることを意味します(IMO)。だから&errno私には合法に見えます。

「それ」や「値」などの言葉が一貫して使用されていることは、標準の作成者が一定ではないことを考えていなかったことを示唆しています。&errno特定のスレッド内で一定ではない実装を想像できると思います&errnoが、脚注が言うように使用するには(ゼロに設定し、ライブラリ関数を呼び出した後にチェックします)、意図的に敵対的である必要があり、おそらく特殊化が必要になるでしょうコンパイラのサポートは敵対的です。

要するに、仕様が non-constant を許可している場合、&errnoそれは意図的なものではないと思います。

[アップデート]

R. はコメントで素晴らしい質問をします。考えてみると、彼の質問と元の質問に対する正しい答えを知っていると思います。親愛なる読者の皆さん、私があなたを納得させることができるかどうか見てみましょう。

R. は、GCC がトップ レベルで次のようなことを許可していると指摘しています。

register int errno asm ("r37");  // line R

errnoこれは、 register に保持されているグローバル値として宣言されますr37。明らかに、それはスレッドローカルの変更可能な左辺値になります。では、適合する C 実装errnoはこのように宣言できますか?

答えはノーです。あなたや私が「宣言」という言葉を使うとき、私たちは通常、口語的で直感的な概念を念頭に置いています. しかし、標準は口語的または直感的に話すものではありません。正確に話し、明確に定義された用語のみを使用することを目的としています。「宣言」の場合、規格自体が用語を定義します。用語を使用するときは、独自の定義を使用しています。

仕様を読むことで、「宣言」とは何か、そうでないものを正確に知ることができます。別の言い方をすれば、標準は言語「C」を記述しています。「C以外の言語」については説明していません。標準に関する限り、「拡張機能付きの C」は「C 以外の言語」にすぎません。

したがって、標準の観点からすると、行 Rはまったく宣言ではありません。解析すらしません!次のように読むこともできます。

long long long __Foo_e!r!r!n!o()blurfl??/**

仕様に関する限り、これは行 R と同じくらい「宣言」です。つまり、まったくありません。

したがって、C11 仕様に記載されているセクション 6.5.3.2 では、次のようになります。

単項演算子のオペランドは&、関数指定子、単項演算子[]または単項演算子の結果、または*ビットフィールドではなく、レジスタ ストレージ クラス指定子で宣言されていないオブジェクトを指定する左辺値のいずれかでなければなりません。

...それは、Line R のようなものを参照しない、非常に正確な何かを意味します。

ここで、参照先のint オブジェクトの宣言を考えてみましょうerrno。(注: errno nameの宣言を意味しているわけではありません。もちろんerrno、たとえばマクロの場合、そのような宣言は存在しない可能性があるためです。つまり、基になるintオブジェクトの宣言を意味します。)

上記の言語は、ビットフィールドを指定するか、「宣言された」オブジェクトを指定しない限り、左辺値のアドレスを取得できることを示していますregister。そして、基礎となるerrnoオブジェクトの仕様は、それがintスレッドローカル期間を持つ変更可能な左辺値であると述べています。

errnoさて、仕様では、基礎となるオブジェクトを宣言する必要があるとはまったく述べていないのは事実です。たぶん、実装定義のコンパイラマジックを介して表示されるだけです。ただし、仕様で「レジスタ ストレージ クラス指定子を使用して宣言されている」と記載されている場合、独自の用語が使用されています。

そのため、基になるオブジェクトが標準的な意味で「宣言」されているかのどちらかであり、その場合、両方とスレッドローカルerrnoであることはできません。registerまたはまったく宣言されていない場合は、宣言されていませんregister。いずれにせよ、これは左辺値であるため、そのアドレスを取得できます。

(ビットフィールドでない限り、ビットフィールドは type のオブジェクトではないことに同意すると思いますint。)

于 2012-10-31T03:12:30.720 に答える
4

の元の実装はerrno、さまざまな標準Cライブラリコンポーネントがエラーに遭遇した場合にエラー値を示すために使用するグローバルint変数としてのものでした。ただし、当時でも、エラーを処理しているときに別の値に設定される可能性のある再入可能なコードやライブラリ関数呼び出しに注意する必要がありました。通常、他の関数またはコードの一部が明示的にまたはライブラリ関数呼び出しを介してerrno値を設定する可能性があるためにエラーコードが長期間必要になった場合は、値を一時変数に保存します。errno

したがって、このグローバルintの元の実装では、演算子のアドレスを使用し、アドレスに依存して一定のままにすることが、ライブラリのファブリックにほぼ組み込まれていました。

ただし、マルチスレッドでは、単一のグローバルを持つことはスレッドセーフではなかったため、単一のグローバルは存在しなくなりました。したがって、おそらく割り当てられた領域へのポインタを返す関数を使用して、スレッドローカルストレージを持つという考え。したがって、次の完全に架空の例のような構成が表示される場合があります。

#define errno (*myErrno())

typedef struct {
    // various memory areas for thread local stuff
    int  myErrNo;
    // more memory areas for thread local stuff
} ThreadLocalData;

ThreadLocalData *getMyThreadData () {
    ThreadLocalData *pThreadData = 0;   // placeholder for the real thing
    // locate the thread local data for the current thread through some means
    // then return a pointer to this thread's local data for the C run time
    return pThreadData;
}

int *myErrno () {
    return &(getMyThreadData()->myErrNo);
}

次にerrno、スレッドセーフなint変数ではなく、単一のグローバルであるかのように使用さerrno = 0;if (errno == 22) { // handle the errorますint *pErrno = &errno;。これはすべて機能します。これは、最終的にスレッドローカルデータ領域が割り当てられ、配置されたままで移動しないためです。マクロ定義は、実際の実装の配管を隠しているerrnoように見えます。extern int

errno値にアクセスしているときに、ある種の動的な割り当て、クローン作成、削除のシーケンスを使用して、スレッドのタイムスライス間で突然シフトするアドレスを設定する必要はありません。タイムスライスがアップすると、タイムスライスがアップします。何らかの同期が必要な場合や、タイムスライスの期限が切れた後もCPUを維持する方法がない限り、スレッドローカルエリアを移動させることは、私にとって非常に厄介な提案のようです。

これは、定数値はスレッド間で異なりますが、特定のスレッドに定数値を与える演算子のアドレスに依存できることを意味します。errnoライブラリ関数が呼び出されるたびにある種のスレッドローカルルックアップを実行するオーバーヘッドを減らすために、のアドレスを使用してライブラリをよく見ることができます。

スレッド内でアドレスをerrno定数として持つことで、errno.hインクルードファイルを使用していた古いソースコードとの下位互換性も提供されます( errnoについては、Linuxのこのマニュアルページをextern int errno;参照してください。昔な日々)。

extern int errno;私が標準を読む方法は、使用されるときに古いものと同様のセマンティクスと構文を維持しながら、この種のスレッドローカルストレージをerrno許可し、サポートされていない組み込みデバイス用のある種のクロスコンパイラでも古い使用法を許可することですマルチスレッド。ただし、マクロ定義を使用しているため構文が類似している可能性があるため、古いスタイルのショートカット宣言は実際の宣言ではないため、使用しないでくださいerrno

于 2012-11-01T03:27:14.493 に答える
0

反例を見つけることができます。ビットフィールドは type を持つintことerrnoができるため、ビットフィールドになる可能性があります。その場合は&errno無効となります。標準の動作は、ここでは明示的に記述できるとは言っていない&errnoため、未定義の動作の定義がここに適用されます。

C11 (n1570), § 4. 適合性
未定義の動作は、この国際規格では「未定義の動作」という言葉によって、または動作の明示的な定義の省略によって示されます。

于 2012-10-27T15:00:42.680 に答える