13

ここに私の最も物議を醸す答えの1つを投稿した後、私はいくつかの質問をすることを敢えてし、最終的に私の知識のいくつかのギャップを埋めます。

この種の式は、それ自体が単なる式ではなく、ポインターと左辺値であると((type_t *) x)仮定して、有効な左辺値と見なされないのはなぜですか?x

多くの人が「標準では許可されていない」と言うことは知っていますが、論理的な観点からは合理的と思われます。規格がそれを禁止している理由は何ですか?結局のところ、任意の2つのポインターは同じサイズであり、ポインター型は、ポインター演算を実行するときに適用する必要がある適切なオフセットを示すコンパイル時の抽象化にすぎません。

4

8 に答える 8

8

キャストの結果は、それ自体が左辺値になることはありません。しかし*((type_t *) x)、左辺値です。

于 2011-09-16T15:13:34.710 に答える
8

さらに良い例として、単項+は、と同様に右辺値を生成しx+0ます。

根本的な理由は、キャストを含むこれらすべてのものが新しい価値を生み出すことです。すでに存在する型に値をキャストすると、同様に新しい値が作成されます。異なる型へのポインタが同じ表現であるかどうかは気にしないでください。場合によっては、新しい値が古い値と同じになることもありますが、原則として新しい値であり、古いオブジェクトへの参照として使用することを意図していないため、右辺値になります。

これらを左辺値にするために、標準では、左辺値で使用されたときに特定の操作が新しい値ではなく古いオブジェクトへの参照になるという特殊なケースを追加する必要があります。AFAIKこれらの特別な場合には大きな需要はありません。

于 2011-09-16T15:29:15.050 に答える
4

一般にキャスト式は左辺値を生成しないためです。

于 2011-09-16T15:14:05.293 に答える
2

さて、キャストは型変換を実行します。一般に、型変換は重要な操作であり、値の表現を完全に変更します。このような状況では、変換の結果が左辺値になる可能性がないことは非常に明白です。

たとえば、変数がある場合は、それをタイプにint i = 0; 変換できます。この変換の結果が左辺値になるとどのように期待できますか?つまり、それはまったく意味がありません。あなたはどうやらできることを期待しているようです...または、前の例の値は、タイプがタイプと同じサイズでさえない可能性があることを考えると、どうなるでしょうか?そして、同じサイズだったとしても、どうなると思いますか?double(double) i(double) i = 3.0;double *p = &(double) i;idoubleint

同じサイズのすべてのポインタに関する仮定は正しくありません。一般的なC言語では(いくつかの例外を除いて)、ポインターの種類が異なれば、サイズ、内部表現、配置要件も異なります。それらが同じ表現を持つことが保証されていたとしても、ポインターを他のすべてのタイプから分離し、明示的なキャストの状況で特別な扱いをする必要がある理由はまだわかりません。

最後に、ここで提唱しているように見えるのは、元の変換では、あるポインタ型を別のポインタ型としてrawメモリで再解釈する必要があるということです。ほとんどすべての場合、生のメモリの再解釈はハックです。このハックを言語機能のレベルに引き上げる必要がある理由は、私にはまったくわかりません。

これはハックであるため、このような再解釈を実行するには、ユーザーの意識的な努力が必要です。例でそれを実行したい場合は、実行する必要があります。これは、実際に型の左辺値として*(type_t **) &x再解釈されます。しかし、単なることで同じことを許可することは、C言語の設計原則とは完全に切り離された災害になります。xtype_t *(type_t *) x

于 2011-09-16T16:20:41.043 に答える
2

実際、あなたは正しいと同時に間違っています。

Cには、任意の左辺値を任意の左辺値に安全に型キャストする機能があります。ただし、構文は単純なアプローチとは少し異なります。

左辺値ポインターは、Cで次のように異なるタイプの左辺値ポインターにキャストできます。

char *ptr;

ptr = malloc(20);
assert(ptr);
*(*(int **)&ptr)++ = 5;

すべての位置合わせ要件を満たすために必要なため、これmalloc()も許容できる使用法です。ただし、以下は移植性がなく、特定のマシンでの位置合わせが間違っているために例外が発生する可能性があります。

char *ptr;

ptr = malloc(20);
assert(ptr);
*ptr++ = 0;
*(*(int **)&ptr)++ = 5;  /* can throw an exception due to misalignment */

要約すると:

  • ポインタをキャストすると、右辺値になります。
  • ポインタで使用*すると、左辺値になります(*ptrに割り当てることができます)。
  • ++(のように*(arg)++)を操作するには左辺値が必要です(左辺値argである必要があります)

したがって、は左辺値であるため((int *)ptr)++失敗しますが、そうではありません。は、と書き直すことができますが、キャストが原因で失敗し、純粋な右辺値になります。ptr(int *)ptr++((int *)ptr += 1, ptr-1)(int *)ptr += 1


これは言語の欠点ではないことに注意してください。キャストは左辺値を生成してはなりません。以下を見てください:

(double *)1   = 0;
(double)ptr   = 0;
(double)1     = 0;
(double *)ptr = 0;

最初の3つはコンパイルされません。なぜ誰かが4行目がコンパイルされることを期待するのでしょうか?プログラミング言語は、そのような驚くべき振る舞いを決して公開してはなりません。さらに、これはプログラムの動作が不明確になる可能性があります。検討:

#ifndef DATA
#define DATA double
#endif
#define DATA_CAST(X) ((DATA)(X))

DATA_CAST(ptr) = 3;

これはコンパイルできませんよね?しかし、あなたの期待が成り立つならば、これは突然cc -DDATA='double *'!でコンパイルされます。安定性の観点から、特定のキャストにそのようなコンテキスト左辺値を導入しないことが重要です。

Cの正しいことは、左辺値があるかどうかということです。これは、意外かもしれない任意のコンテキストに依存してはなりません。


Jensが指摘したように、左辺値を作成する演算子はすでに1つあります。これは、ポインターの間接参照演算子である「単項*」です(のように*ptr)。

として書くことができ、*ptrとして書くことができることに注意してください。配列の添え字は左辺値であるため、左辺値も同様です。0[ptr]*ptr++0[ptr++]*ptr

待って、何? 0[ptr]エラーに違いないですよね?

実は違う。それを試してみてください!これは有効なCです。次のCプログラムは、すべての点でIntel 32/64ビットで有効であるため、コンパイルして正常に実行されます。

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>

int
main()
{
  char *ptr;

  ptr = malloc(20);
  assert(ptr);

  0[(*(int **)&ptr)++] = 5;
  assert(ptr[-1]==0 && ptr[-2]==0 && ptr[-3]==0 && ptr[-4]==5);

  return 0;
}

Cでは、両方を持つことができます。左辺値を作成しないキャスト。そして、ある意味でキャストを使用する機能。これにより、左辺値プロパティを存続させることができます。

ただし、キャストから左辺値を取得するには、さらに2つの手順が必要です。

  • キャストする前に、元の左辺値のアドレスを取得します。左辺値なので、いつでもこのアドレスを取得できます。
  • 目的のタイプのポインターにキャストします(通常、目的のタイプはポインターでもあるため、そのポインターへのポインターがあります)。
  • キャスト後、この追加のポインターを逆参照します。これにより、左辺値が再び得られます。

したがって、間違っている代わりに、*((int *)ptr)++を書くことができます*(*(int **)&ptr)++。これはまたptr、この式ですでに左辺値でなければならないことを確認します。または、Cプリプロセッサを使用してこれを作成するには:

#define LVALUE_CAST(TYPE,PTR) (*((TYPE *)&(PTR)))

したがって、渡されたvoid *ptrもの(に偽装する可能性がありますchar *ptr)については、次のように書くことができます。

*LVALUE_CAST(int *,ptr)++ = 5;

通常のポインタ演算の警告(プログラムの異常終了または互換性のないタイプでの未定義の動作。これは主にアラインメントの問題に起因します)を除いて、これは適切なCです。

于 2016-12-30T19:13:28.933 に答える
1

C標準は、すべてのポインテッドタイプのCポインターモデルを実装するために奇妙なハックを必要とするエキゾチックなマシンアーキテクチャをサポートするために作成されました。コンパイラーがポイント先の型ごとに最も効率的なポインター表現を使用できるようにするために、C標準では互換性のある異なるポインター表現を必要としません。このようなエキゾチックなアーキテクチャでは、voidポインタ型は最も一般的で、したがって最も遅いさまざまな表現を使用する必要があります。C FAQには、このような現在廃止されているアーキテクチャの具体例がいくつかあります。http: //c-faq.com/null/machexamp.html

于 2011-09-16T16:10:58.473 に答える
1

xがポインタ型の場合、*(type_t **)&xは左辺値であることに注意してください。ただし、おそらく非常に制限された状況を除いて、そのようにアクセスすると、エイリアシング違反のために未定義の動作が発生します。それが合法であるかもしれない唯一の時は、ポインタ型が対応するsigned/unsignedまたはvoid/charポインタ型であるかどうかですが、それでも私は疑わしいです。

(type_t *)x(T)xタイプTや式に関係なく、は左辺値ではないため、は左辺値ではありませんx(type_t *)の特殊なケースです(T)

于 2011-09-16T16:35:35.050 に答える
1

トップレベルから見ると、一般的には何の役にも立ちません。'((type_t *)x)='の代わりに、xがポインタであると仮定して、'x='を実行することもできます。アドレス'x'が指す値を直接変更したいが、同時にそれを新しいデータ型へのポインタとして解釈した後は、*((type_t **)&x)=が先に進みます。繰り返しますが、((type_t **)&x)=は、有効な左辺値ではないという事実は言うまでもなく、目的を果たしません。

また、((int *)x)++の場合、少なくとも「gcc」が「lvalue」の行に沿って文句を言わない場合は、「x =(int *)x+1」として再解釈される可能性があります。

于 2011-09-17T02:45:33.963 に答える