2011-02-05 31 views
11

節§6.5.3.2言う¶3「アドレスと間接演算子」(関連セクションのみ):C標準

単項&オペレータがそのオペランドのアドレスを返します。 ... オペランドが単精度*演算子の結果である場合、その演算子も&演算子も評価されず、演算子の制約が適用され、結果が左辺値。同様に、オペランドが[]演算子の結果である場合、[]によって暗示される&オペレータも単項*どちらが評価され、&オペレータを除去し、[]オペレータが+オペレータに変更した場合と同様の結果であります。 ...

これは、このことを意味します

#define NUM 10 
int tmp[NUM]; 
int *i = tmp; 
printf("%ti\n", (ptrdiff_t) (&*i - i)); 
printf("%ti\n", (ptrdiff_t) (&i[NUM] - i)); 

が0とNUM(10)を印刷、完全に合法であるべき。標準は、これらのケースの両方を最適化する必要があることは非常に明白です。

しかし、最適化されるように次のことを要求していないようだ。

struct { int a; short b; } tmp, *s = tmp; 
printf("%ti\n", (ptrdiff_t) (&s->b - s)); 

これはひどく矛盾しそうです。私は上記のコードがsizeof(int)に加えて(おそらく)パディング(おそらくは4)を印刷してはならないという理由は見当たりません。

簡略化&->式は、単純なアドレス+オフセットである&[]と概念的に(IMHO)同じになります。 []演算子で実行される可能性があるのではなく、コンパイル時に確定できるオフセットですらあります。

なぜこのように一見矛盾しているのかという根拠には何かがありますか?

+0

ハードコアとC++の標準的な質問を見たことがありますが、私は何かをやっているだけで何もしていない、好きな人は答えを読むのを待っています。実際に書いたのは変だと感じます。 –

+0

興味深い... MSVC++は4枚印刷します! – Abhi

+0

@Abhi Rao - -Wall -Wextra -Werrorを持つGCC(4.0)は、苦情なしでコンパイルして4を出力します。 –

答えて

4

例では、&i[10]は実際には無効です。i + 10になります。これはNULL + 10になり、NULLポインタでは算術演算を実行できません。 (6.5.6/8は、ポインタ演算が実行される条件をリストしています)

とにかく、このルールはC99で追加されました。 C89には存在しませんでした。最後の行がC89で技術的に無効である(とC++の)こと

int* begin, * end; 
int v[10]; 

begin = &v[0]; 
end = &v[10]; 

が、このルールのためC99で許可されている:私の理解では、それが明確に定義された以下のようなコードを作るために大部分が追加されたことです。これは、一般的に使用される構造を明確に定義した比較的小さな変更でした。

nullポインタで算術演算を実行できないため、例(&s->b)は無効です。

なぜこの「矛盾」があるのか​​、私は推測するしかありません。一貫性を持たせることを考えていた人は誰もいなかったでしょうし、誰もこれに魅力的なユースケースを見たことはありません。これが考慮され、最終的には拒否された可能性があります。 the Rationaleには&*の減少についてのコメントはありません。あなたはthe WG14 papersでいくつかの決定的な情報を見つけることができるかもしれませんが、残念ながらそれらはかなり組織化されていないように見えるので、それらを通るトロールは面倒かもしれません。

+0

私は実際に私が心配していたものではないので、例からヌルポインタを取り出しました。 –

+0

ここでは、どのように 'NULL'が演奏されるのか分かりません。さらに、ポインタ演算(存在しないオブジェクトを評価しない限り)の場合、配列の直後の要素を使用することができます。 AFAIR、これは標準のいくつかの場所で言及されています。 –

+0

@ Jens:この質問の元の例は、 'NULL'ポインタを使用しており、明確な算術演算はNULLポインタに対して実行されません。あなたは1つの終わりの "要素"へのポインタを得ることができますが、逆参照することはできません。 'int v [10];'に対して、 '&v [10]'や '&*(v + 10)'を使うのはC99だけです。 C++およびC90では、このようなコードは正式に未定義の動作をもたらします。 –

1

私は、コンパイラがさまざまな方法でパックすることを選択できると考えています。メモリアクセス速度を向上させるために、構造体のメンバー間にパディングを追加する可能性があります。つまり、b、常には4のオフセットになるとは限りません。単一の値には同じ問題はありません。

また、コンパイラは、最適化フェーズ中にメモリ内の構造体のレイアウトを知りません。したがって、構造体メンバアクセスおよびそれに続くポインタキャストに関する最適化が行われません。


編集:

私は別の理論を持っている...

何度コンパイラは、単に字句解析と構文解析後に抽象構文木を最適化します。つまり、キャンセルする演算子や定数に評価される式など、ツリーの各セクションを1つのノードに縮小するようなものが見つかることになります。これは、構造体に関する情報が利用できないことを意味します。。後の最適化パスは、追加の情報があるため、これを考慮に入れるコード生成が行われた後に発生しますが、ASTのトリミングなどの情報はまだ存在しません。

+1

それは常に4のオフセットになるとは確信できませんが、 'struct'が有用であるためには、それが一定のオフセットになることを確かめることができます。そして、 'int'の後ろに' short'がついていたので、コンパイラの間にパディングを入れる必要があるとは思っていません。 –

+0

"コンパイラは、最適化フェーズ中にメモリ内の構造体のレイアウトを知りません..."これは、オプティマイザにとって必要不可欠な情報のようです。 –

+0

私はそれがコンパイラがどのように書かれているかにもよると思います。標準ではどのように正常に動作するべきかに関するルールが規定されていますが、最適化フラグを設定することはおそらくあなたが言うように行います。私の推測では、標準の作者はあまりにも多くの最適化を課したくないということです。 –

2

私はこのルールが最適化の目的で追加されていないと考えていますが、それがないと未定義の動作となる&t[sizeof(t)/sizeof(*t)]&*(t+sizeof(t)/sizeof(*t))直接愚かなように見えるかもしれませんが、レイヤーまたは2つのマクロを追加して意味をなすことができます)。特別なケーシング& p-> mがそのような利益をもたらすケースは見られません。 Jamesが指摘したように、pがnullのポインタを持つ&p[10]はまだ未定義の動作です。 &p->mとpがヌルポインタの場合も、同様に無効な状態になります(pがヌルポインタの場合は何も表示されません)。

+0

'p(NULL)'が '&((struct t *)0) - > m 'に依存している' offsetof'マクロの実装である場合、明白な(IMHO)の使用法です。しかし、 '0'の代わりに '1'(またはスタックのようなコンパイラ依存の有効なポインタ値)に変更するのと同じように簡単に変更できますが、おそらくあなたに良い' struct'を与える可能性は低いでしょう。値は正しいオフセット値を与えるはずです。 –

+0

@Chris:以前は、初期の標準Cコンパイラでoffsetof()をアドレス0で定義していましたが、コアダンプやコンパイルエラーがありました。私はシステムのヘッダーをハックアップし、0の代わりにアドレスとして1024を使いました。それはうまくいった。それは問題とならないように十分に整列されています - 1とは異なります。 –

+0

文字配列を除いて、 '&t [sizeof(t)]'は割り当てられたオブジェクトの終わりをはるかに超えます。 –