ポインタのこの使用法を予測不可能にするのは何ですか?


108

私は現在ポインタを学んでおり、私の教授は例としてこのコードを提供しました:

//We cannot predict the behavior of this program!

#include <iostream>
using namespace std;

int main()
{
    char * s = "My String";
    char s2[] = {'a', 'b', 'c', '\0'};

    cout << s2 << endl;

    return 0;
}

彼はコメントで、プログラムの動作を予測することはできないと書いています。正確には何が予測不可能になるのでしょうか?私はそれで何も悪いことを見ません。


2
教授のコードを正しく再現しましたか?このプログラムが「予測できない」動作を引き起こす可能性があると主張することは正式に可能ですが、そうすることは意味がありません。そして、どんな教授も、学生に「予測できない」ことを説明するために難解なものを使用することはないと思います。
AnTは

1
Orbitでの@Lightnessレース:コンパイラは、必要な診断メッセージを発行した後、不正な形式のコードを「受け入れる」ことができます。ただし、言語仕様はコードの動作を定義していません。つまり、の初期化でのエラーのためs、プログラムが何らかのコンパイラによって受け入れられた場合、正式には予測できない動作をします。
AnTの

2
@TheParamagneticCroissant:いいえ。初期化は現代では形式が正しくありません。
オービットのライトネスレース

2
@The常磁性クロワッサン:上で述べたように、この言語は「コンパイルに失敗」するために不正な形式のコードを必要としません。コンパイラは、診断を発行するために必要です。その後、継続してコードを「正常に」コンパイルすることが許可されます。ただし、このようなコードの動作は言語仕様では定義されていません。
AnTは

2
あなたの教授があなたにどんな答えを出したか知りたいです。
ダニエルW.クロンプトン2015

回答:


125

プログラムの形式が正しくないため、プログラムの動作は存在しません。

char* s = "My String";

これは違法です。2011年より前は、12年間非推奨でした。

正しい行は次のとおりです。

const char* s = "My String";

それ以外は、プログラムは問題ありません。あなたの教授はより少ないウイスキーを飲むべきです!


10
main.cpp:6:16:警告:ISO C ++は文字列定数を 'char *'に変換することを禁止します[-Wpedantic]
marcinj

17
@ブラック:いいえ、変換が違法であるという事実はプログラムを不正な形式にします。これは廃止された、過去に。私たちはもはや過去ではありません。
オービットのライトネスレース

17
(これは12年の廃止の目的だったので馬鹿げています)
オービットのライトネスレース

17
@black:不正な形式のプログラムの動作は「完全に定義」されていません
オービットのライトネスレース

11
いずれにしても、問題はC ++に関するものであり、GCCの特定のバージョンに関するものではありません。
オービットのライトネスレース

81

答えは、コンパイルするC ++標準によって異なります。次の行を除いて、すべてのコードはすべての標準で完全に整形式です‡。

char * s = "My String";

現在、文字列リテラルには型がconst char[10]あり、それへの非constポインターを初期化しようとしています。char文字列リテラルのファミリー以外のすべてのタイプでは、そのような初期化は常に違法でした。例えば:

const int arr[] = {1};
int *p = arr; // nope!

ただし、C ++ 11より前では、文字列リテラルの場合、§4.2/ 2に例外がありました。

ワイド文字列リテラルではない文字列リテラル(2.13.4)は、タイプ " pointer to char "の右辺値に変換できます。[...]。どちらの場合も、結果は配列の最初の要素へのポインターです。この変換は、明示的な適切なポインターターゲットタイプがある場合にのみ考慮され、左辺値から右辺値への変換が一般的に必要な場合は考慮されません。[注:この変換は非推奨です。付録Dを参照してください。]

したがって、C ++ 03では、コードは完全に問題なく(非推奨ですが)、明確で予測可能な動作を持っています。

C ++ 11では、そのブロックは存在しません-に変換された文字列リテラルにそのような例外はありません。そのchar*ため、コードは、int*先ほど提供例と同じように。コンパイラーは診断を発行する義務があります。理想的には、C ++型システムの明らかな違反であるこのような場合、理想的なコンパイラーはこの点(たとえば、警告を発行すること)だけでなく、失敗することを期待します。あからさま。

コードは、理想的にはコンパイルしないでください。ただし、gccとclangの両方で実行します(このタイプのシステムホールが10年以上廃止されているにもかかわらず、おそらく多くのコードが壊れているため、ほとんど利益が得られないためです)。コードの形式が正しくないため、コードの動作がどうなるかを推論しても意味がありません。しかし、この特定のケースとそれが以前に許可されていた歴史を考えると、結果のコードを暗黙的const_castなものであるかのように解釈するのは不当なストレッチとは思えません。

const int arr[] = {1};
int *p = const_cast<int*>(arr); // OK, technically

これで、実際には二度と触れないので、プログラムの残りの部分は完全にうまくいきますs読書作成されたconstオブジェクトをconstポインタ以外で、まったく問題ありません。そのようなポインタを介してcreated- objectを書き込むことconstは未定義の動作です:

std::cout << *p; // fine, prints 1
*p = 5;          // will compile, but undefined behavior, which
                 // certainly qualifies as "unpredictable"

sコードのどこにも変更がないため、プログラムはC ++ 03では問題なく、C ++ 11でのコンパイルに失敗するはずですが、とにかく実行されます-コンパイラーが許可する場合、未定義の動作はまだありません† 。コンパイラがC ++ 03ルールを[誤って]解釈していることを考慮して、「予測できない」動作につながるようなものは何もありません。でも書いてs、すべての賭けはオフです。C ++ 03とC ++ 11の両方。


ただし、不適切な形式のコードは当然のことながら、妥当な動作を期待
できません。ただし、そうでない場合を除き、Matt McNabbの回答を参照してください


ここで「予測不可能」とは教授が意図したものであり、標準を使用してコンパイラが不正なコードで何を行うかを予測できないことを意味します(診断を発行する以外に)。はい、それはC ++ 03がそれを処理する必要があると言っているようにそれを処理することができ、(「真のスコットランド人ではない」誤解のリスクがある)常識により、これが賢明なコンパイラライターだけであると確信を持って予測できますコードをコンパイルするかどうかを選択します。次に、それを文字列リテラルを非constにキャストする前に逆にする意味として扱うことができます。標準C ++は関係ありません。
スティーブジェソップ2015

2
@SteveJessopその解釈は買わない。これは未定義の動作でも、標準が診断の必要がないとラベル付けした不正なコードのカテゴリでもありません。これは単純な型システム違反であり、非常に予測可能である必要があります(C ++ 03ではコンパイルおよび通常の処理を行い、C ++ 11ではコンパイルに失敗します)。コンパイラのバグ(または芸術的ライセンス)を使用してコードが予測不可能であることを示唆することはできません。そうしないと、すべてのコードがトートロジー的に予測不可能になります。
2015

私はコンパイラのバグについて話しているのではなく、標準がコードの動作(もしあれば)を定義するかどうかについて話している。教授も同じことをしているのではないかと思いますが、「予測不可能」というのは、現在の標準では動作が定義されていない、と言ったのと同じです。とにかく、それは私には、教授がこれが未定義の振る舞いを備えた整形式のプログラムであると誤って信じているというよりも、より可能性が高いようです。
スティーブジェソップ2015

1
いいえ、違います。この規格は、不正な形式のプログラムの動作を定義していません。
Steve Jessop、2015

1
@supercat:それは公平な点ですが、それが主な理由だとは思いません。標準が不正な形式のプログラムの動作を指定していない主な理由は、コンパイラが(Objective Cのように)整形式ではない構文を追加することで言語の拡張をサポートできるためです。コンパイルが失敗した後のクリーンアップを完全に回避する実装を許可することは、単なるボーナスです:-)
Steve Jessop

20

他の回答では、const char配列へのの割り当てにより、このプログラムはC ++ 11で形式が正しくないことがカバーされていchar *ます。

ただし、このプログラムはC ++ 11より前でも不正な形式でした。

operator<<オーバーロードです<ostream>iostream含めるための要件ostreamはC ++ 11で追加されました。

歴史的に、ほとんどの実装にiostreamostream、おそらく実装を容易にするため、またはおそらくより良いQoIを提供するためにいずれにせよました。

ただし、オーバーロードを定義せずiostreamostreamクラスのみを定義することは適合しますoperator<<


13

このプログラムで私が目にする唯一の少し間違ったことは、文字列リテラルをミュータブルに割り当てることを想定していないことです charポインターに、これは多くの場合コンパイラー拡張として受け入れられます。

それ以外の場合、このプログラムは私には明確に定義されています。

  • パラメータなどとして渡されたときに文字配列が文字ポインタになる方法を指示するルール(など) cout << s2)明確に定義されています。
  • 配列はnullで終了します。これは、 operator<<char*(またはa const char*)の。
  • #include <iostream>にはが含まれ<ostream>、これによりが定義operator<<(ostream&, const char*)されるため、すべてが適切に表示されます。

12

上記の理由により、コンパイラの動作を予測することはできません。(それコンパイルに失敗する失敗しない場合があります。)

コンパイルが成功した場合、動作は明確に定義されています。あなたは確かにプログラムの動作を予測することができます。

コンパイルに失敗した場合、プログラムはありません。コンパイルされた言語では、プログラムは実行可能ファイルであり、ソースコードではありません。実行可能ファイルがない場合、プログラムがないため、存在しないものの動作について話すことはできません。

だから私はあなたの教授の発言が間違っていると思います。このコードに直面したときのコンパイラの動作は予測できませんが、プログラムの動作とは異なります。だから、彼が幼虫を選ぶつもりなら、彼は彼が正しいことを確認した方がいいです。または、もちろん、あなたは彼を誤って引用した可能性があり、間違いは彼の言ったことの翻訳にあります。


10

他の人が指摘したように、コードは以前のバージョンでは有効でしたが、C ++ 11では不正です。したがって、C ++ 11のコンパイラーは、少なくとも1つの診断を発行する必要がありますが、コンパイラーの動作またはビルドシステムの残りの部分は、それ以上は規定されていません。規格では、コンパイラがエラーに応答して突然終了することを禁止せず、リンカが有効であると考える可能性がある部分的に書き込まれたオブジェクトファイルを残して、壊れた実行可能ファイルを生成します。

優れたコンパイラは、生成されることが予想されるオブジェクトファイルが有効であるか、存在しないか、無効であると認識できるかを、終了する前に常に確認する必要がありますが、このような問題は規格の管轄外です。歴史的には(あるかもしれませんが)いくつかのプラットフォームでは、コンパイルが失敗すると、ロード時に正当な外観の実行可能ファイルが任意の方法でクラッシュする可能性があります(リンクエラーが頻繁に発生するシステムで作業する必要がありました)。 、私は構文エラーの結果が一般的に予測不可能であるとは言いません。優れたシステムでは、ビルドを試行すると、通常、コード生成時にコンパイラーの最善の努力で実行可能ファイルが生成されるか、まったく実行可能ファイルが生成されません。一部のシステムは、ビルドが失敗した後、古い実行可能ファイルを残します、

私の個人的な好みは、ディスクベースのシステムが出力ファイルの名前を変更し、実行可能ファイルが役立つ可能性があるというまれな場合に備えて、新しいコードが実行されていると誤って信じることによる混乱を回避し、埋め込みプログラミングを行うことです有効な実行可能ファイルが通常の名前(理想的には使用可能なプログラムの欠如を安全に示すもの)で利用できない場合に、プログラマーがプロジェクトごとにロードするプログラムを指定できるシステム。組み込みシステムのツールセットは、通常、そのようなプログラムが何をすべきかを知る方法がありませんが、多くの場合、システムの「実際の」コードを書いている人は、簡単に適応できるハードウェアテストコードにアクセスできます。目的。名前を変更する動作を見たことはありませんが、

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.