#include <stdio.h>
int main()
{
printf("%s", (1)["abcd"]+"efg"-'b'+1);
}
このコードの出力が次のようになる理由を説明してください。
fg
(1)["abcd"]
ポイントは知っていますが、有効な構文でさえあるのは"bcd"
なぜですか?+"efg"-'b'+1
I know (1)["abcd"] points to "bcd"
No.(1)["abcd"]
は一文字(b
)です。
そう(1)["abcd"]+"efg"-'b'+1
です:'b' + "egf" - 'b' + 1
そして、それを単純化すると、 になり"efg" + 1
ます。したがって、それは印刷しfg
ます。
注:上記の回答は、C 言語の仕様に従って厳密に合法ではない観察された動作のみを説明しています。理由は次のとおりです。
ケース 1: 'b' < 0
または'b' > 4
この場合、部分式が無効なポインタ式を生成するため、式は未定義の動作につながります ( C11、6.5.5乗法(1)["abcd"] + "efg" - 'b' + 1
演算子--以下の引用)。(1)["abcd"] + "efg"
'b' + "efg"
広く使用されているASCII文字セットで'b'
は98
、10 進数です。あまり広く使用されていないEBCDIC文字セットで'b'
は130
、10 進数です。したがって、サブ式(1)["abcd"] + "efg"
は、これら 2 つのいずれかを使用するシステムで未定義の動作を引き起こします。
そのため、奇妙なアーキテクチャを除けば'b' <= 4 and 'b' >= 0
、このプログラムは、C 言語の定義方法が原因で未定義の動作を引き起こす可能性があります。
C11、5.1.2.3 プログラムの実行
この国際規格のセマンティック記述は、最適化の問題が無関係な抽象マシンの動作を記述しています。[...] 抽象マシンでは、すべての式がセマンティクスで指定されたとおりに評価されます。実際の実装では、値が使用されておらず、必要な副作用が生成されていないと推測できる場合、式の一部を評価する必要はありません。
これは、抽象機械の動作に基づいて標準全体が定義されていることを断固として述べています。
したがって、この場合、未定義の動作が発生します。
ケース 2: 'b' >= 0
または'b' <= 4
(これは非常に想像上のものですが、理論的には可能です)。
この場合、部分式(1)["abcd"] + "efg"
は有効である可能性があります (そして、式全体(1)["abcd"] + "efg" - 'b' + 1
)。
文字列リテラル"efg"
は 4 文字で構成され、これは (C の型の) 配列型でchar[N]
あり、C 標準は (上で引用したように) 配列の末尾を 1 つ過ぎて評価されるポインター式がオーバーフローしたり、エラーを引き起こしたりしないことを保証します。未定義の動作。
(1) "efg"+0
(2) "efg"+1
(3) "efg"+2
(4)"efg"+3
および (5)"efg"+4
は、C 標準が次のように述べているためです。
C11、6.5.5 乗法演算子
整数型の式をポインターに加算またはポインターから減算すると、結果はポインター オペランドの型になります。ポインターオペランドが配列オブジェクトの要素を指し、配列が十分に大きい場合、結果は元の要素からオフセットされた要素を指し、結果と元の配列要素の添字の差が整数式と等しくなります。つまり、式 P が配列オブジェクトの i 番目の要素を指している場合、式 (P)+N (同等に、N+(P)) および (P)-N (N の値は n) が指します。それぞれ、配列オブジェクトの i+n 番目と i−n 番目の要素に、それらが存在する場合。さらに、式 P が配列オブジェクトの最後の要素を指している場合、式 (P)+1 は配列オブジェクトの最後の要素の 1 つ後ろを指し、また、式 Q が配列オブジェクトの最後の要素の 1 つ後ろを指す場合、式 (Q)-1 は配列オブジェクトの最後の要素を指します。ポインターオペランドと結果の両方が同じ配列オブジェクトの要素を指している場合、または配列オブジェクトの最後の要素の 1 つ後ろを指している場合、評価はオーバーフローを生成しません。それ以外の場合、動作は未定義です。結果が配列オブジェクトの最後の要素の 1 つ後ろを指している場合、評価される単項 * 演算子のオペランドとして使用されません。それ以外の場合、動作は未定義です。結果が配列オブジェクトの最後の要素の 1 つ後ろを指している場合、評価される単項 * 演算子のオペランドとして使用されません。それ以外の場合、動作は未定義です。結果が配列オブジェクトの最後の要素の 1 つ後ろを指している場合、評価される単項 * 演算子のオペランドとして使用されません。
したがって、この場合、未定義の動作は発生しません。
C標準の関連部分を掘り下げてくれた@zchと@Keith Thompsonに感謝します:)
他の 2 つの回答の違いについては、多少の混乱があるようです。手順を追って説明すると、次のようになります。
(1)["abcd"]+"efg"-'b'+1
最初の部分で(1)["abcd"]
は、配列が C で処理される方法を利用しています。以下を見てみましょう。
int a[5] = { 0, 10, 20, 30, 40 };
printf("%d %d\n", a[2], 2[a]);
出力は になります20 20
。なんで?の配列の名前はint
そのアドレスに評価され、そのデータ型はへのポインターでint
あるためです。整数配列の要素を参照すると、C は配列のアドレスにオフセットを追加し、結果を type として評価しint
ます。しかし、これは Cが順序を気にしないことを意味します:a[2]
は とまったく同じ2[a]
です。
同様に、a
は配列のアドレスであるため、a + 1
は配列への最初のオフセットにある要素のアドレスです。もちろん、それは と同等1 + a
です。
C の文字列は、 type の配列を表現するための、人間にわかりやすいもう 1 つの方法ですchar
。そのため、最初のオフセットにある要素を文字の配列に返すのと(1)["abcd"]
同じです、、、、、 ...文字です。a
b
c
d
\0
b
C では、すべての文字に整数値 (通常はその ASCII コード) があります。の値はb
たまたま 98 です。したがって、評価の残りの部分には、整数と配列 (文字列) を使用した計算が含まれます"efg"
。
文字列のアドレスがあります。98 (文字 の ASCII 値b
) を加算および減算し、1 を加算b
します。キャラクターf
。
の%s
変換はprintf()
、アドレスを文字列の最初の文字として扱い、最後に null 文字が現れるまで文字列全体を出力するように C に指示します。
これは、で始まるfg
文字列の一部です。"efg"
f