なんて挑発的な質問だ!
このスレッドでの応答とコメントの大まかなスキャンでも、一見単純で単純なクエリがいかに感動的かがわかります。
それは驚くべきことではありません。
間違いなく、の概念と使用に関する誤解ポインタのプログラミング全般における重大な障害の主な原因を表しています。
この現実の認識は、対処するために、そして好ましくはポインターが完全にもたらす課題を回避するために特別に設計された言語の遍在性において容易に明らかです。C ++とその他のC、Javaとその関係、Pythonとその他のスクリプトの派生物を考えてみてください。単に、より目立って普及しているものであり、問題への対処の厳しさは多かれ少なかれ順序付けられています。
根本的な原則をより深く理解することは、 、関連することを熱望するすべての個々の卓越したプログラミングで-特にシステムレベルで。
これはまさにあなたの先生が示すことを意味していると思います。
また、Cの性質により、Cはこの探索に便利な手段になります。アセンブリほど明確ではありませんが、おそらくより容易に理解できますが、実行環境のより深い抽象化に基づく言語よりもはるかに明確です。
決定論を促進するように設計Cは、プログラマーの意図を機械が理解できる命令に変換おり、システムレベルの言語です。高レベルに分類されていますが、実際には「中」のカテゴリに属しています。しかし、そのようなものが存在しないため、「システム」の指定で十分です。
この特性は、デバイスドライバー、オペレーティングシステムコード、および組み込み実装で、この言語を最適な言語にする責任があります。さらに、最適な効率が最優先であるアプリケーションでは当然のことながら好ましい代替手段です。ここでそれは生存と絶滅の違いを意味するため、贅沢ではなく必需品です。そのような場合、携帯性の魅力的な利便性は、その魅力をすべて失い、光沢のないパフォーマンスのパフォーマンスを選択します。最小公分母のは、考えられないほど有害なオプションになります。
Cとその派生物のいくつかを非常に特別なものにしているのは、ユーザーが望んでいるときに、関連する責任をユーザーに課すことなく、ユーザーが完全に制御できることです。それにもかかわらず、それは機械からの最も薄い断熱材以上のものを決して提供しないので、適切な使用はポインターの概念の厳密な理解を要求します。
本質的に、あなたの質問に対する答えは、あなたの疑いを確認する上で、非常にシンプルで満足できるほど甘いです。ただし、必要な重要性をこのステートメントのすべての概念にします。
- ポインタを、調査し比較し、操作の行為は常にしている必然の結果から得られた結論は、このように必要性を数値の妥当性に依存含まれており、一方で、有効なないこと。
前者は、両方で常に 安全かつ潜在的に 適切な後者の缶がしかなりながら、適切なそれがされたときに確立と安全。驚くべきこと-にいくつか-そう、後者の有効性を確立することに依存して要求元を。
もちろん、混乱の一部は、ポインターの原則内に本質的に存在する再帰の影響と、アドレスとコンテンツを区別することで生じる課題から生じます。
あなたはかなり正確に推測しました、
個々のポインタがどこを指しているかに関係なく、どのポインタも他のポインタと比較できると私は思わされています。さらに、2つのポインター間のポインター演算は、ポインターが格納するメモリアドレスを使用するだけなので、ポインターが個別にどこを指すかに関係なく、問題ないと思います。
そして、いくつかの貢献者は断言しました:ポインタは単なる数字です。時に近いもの複素数にが、それでも数に過ぎないもの。
この論争がここで受け取られた愉快な犯罪は、プログラミングよりも人間の本質についてより多くを明らかにしますが、注目に値し、詳述する価値があります。おそらく後でそうするでしょう...
1つのコメントが示唆し始めると、このすべての混乱と驚きは、有効なものと安全なものを区別する必要性から生じますが、それは単純化しすぎです。また、機能的で信頼できるもの、実用的で適切なもの、さらには特定の状況で適切なものと、より一般的な意味で適切なものとを区別する必要もあります。言うまでもなく; 適合性と妥当性の違い。
そのためには、まずポインタが何であるかを正確に理解する必要があります 。
- あなたはコンセプトをしっかりと把握してきました。他の人と同じように、これらの図は非常に単純化されていると思うかもしれませんが、ここで明らかな混乱のレベルは、説明を簡潔にすることを要求しています。
いくつか指摘しているように、ポインタという用語は単にインデックスであるものの単なる特別な名前であり、したがって、他のどの番号にもすぎません。
これは、既にあるべき自明すべての現代的な主流のコンピュータがあるという事実を考慮して、バイナリのマシン必ずしも動作し、排他的にし、上の数字。量子コンピューティングは変えるませんが、それは非常にありそうにありません、そしてそれは成熟していません。
技術的には、既に述べたように、ポインタはより正確なアドレスです。それらを家の「住所」または通りの区画と相関させることのやりがいのある類推を自然に導入する明白な洞察。
難問をこのような魅力的に複雑なもつれに変えるさらなるねじれに私たちを連れて来てください。上記では、単純さと明快さのために、ポインタはアドレスであると示唆するのが好都合でした。もちろん、これは正しくありません。ポインタはアドレスではありません。ポインタがある参照アドレスには、それが含まれているアドレスを。封筒スポーツのように家への参照。これを熟考すると、概念に含まれている再帰の提案で何が意味されているのかを垣間見る可能性があります。それでも; 私たちはあまりにも多くの単語を持っています、そしてアドレスへの参照のアドレスについて話しますなど、無効なオペコード例外でほとんどの頭脳をすぐに失速させます。そして、ほとんどの場合、意図はコンテキストからすぐに獲得されるので、私たちは通りに戻りましょう。
私たちのこの架空の都市の郵便局員は、「現実の」世界で私たちが見つけるものとよく似ています。無効なアドレスについて話したり問い合わせたりしても、脳卒中になる可能性はほとんどありませんが、その情報に基づいて行動するように依頼すると、最後の人は誰もが頭を痛めます。
私たちの特異な通りに20軒の家しかないと仮定します。さらに、いくつかの誤った、または失読症の魂は今、数71に、手紙、非常に重要なものを指示したことをふり、我々はそのようなアドレスがあるかどうか、私たちのキャリアフランクを求めることができ、彼は簡単かつ冷静になりますレポート:なし。私たちも、彼はそれがあれば、この場所はうそだろうか遠く外の通りを推定することを期待することができなかったが存在する:およそ2.5倍、さらに端部よりも。これによって彼に憤慨が生じることはありません。しかし、この手紙を届けるように、またはその場所からアイテムを受け取るように頼んだ場合、彼は不快感について率直に言って従うことを拒否するでしょう。
ポインタは単なるアドレスであり、アドレスは単なる数値です。
次の出力を確認します。
void foo( void *p ) {
printf(“%p\t%zu\t%d\n”, p, (size_t)p, p == (size_t)p);
}
有効かどうかに関係なく、好きなだけポインタを呼び出してください。してくださいか、それはあなたのプラットフォーム上で失敗した場合、またはお使いの場合は、あなたの調査結果を投稿(現代)コンパイラが文句を言います。
現在、ポインタは単なる数値なので、それらを比較することは必然的に有効です。ある意味で、これはまさにあなたの教師が示していることです。以下のステートメントはすべて完全に有効であり、適切です!- C、およびコンパイル済みの問題に遭遇することなく実行されますどちらのポインタを初期化する必要が、彼らが含まれている値は、したがってすることができるにもかかわらず、未定義:
- 私たちは、計算され
result
、明示的にのために明快さ、および印刷することはして強制的にそうでない場合は冗長、デッドコードがどうなるかを計算するようにコンパイラに。
void foo( size_t *a, size_t *b ) {
size_t result;
result = (size_t)a;
printf(“%zu\n”, result);
result = a == b;
printf(“%zu\n”, result);
result = a < b;
printf(“%zu\n”, result);
result = a - b;
printf(“%zu\n”, result);
}
もちろん、テストの時点でaまたはbのいずれかが未定義(読み取り:正しく初期化されていない)である場合、プログラムの形式は正しくありませんが、これは、ここでの説明のこの部分とはまったく関係ありません。これらのスニペットは、あまりにも次の文のように、されている保証 - 「標準」で-するコンパイルと実行完璧に、かかわらずのIN関わるあらゆるポインタの-validity。
問題は、無効なポインタが逆参照されたときにのみ発生します。フランクに無効な存在しない住所への集荷または配達を依頼した場合。
任意のポインタが与えられた場合:
int *p;
このステートメントはコンパイルして実行する必要がありますが、
printf(“%p”, p);
...これが必要です:
size_t foo( int *p ) { return (size_t)p; }
...次の2つは対照的に、コンパイルは簡単ですが、ポインタが有効でない限り実行に失敗します。これにより、ここでは、現在のアプリケーションがアクセスを許可されているアドレスを参照していることを意味します。
printf(“%p”, *p);
size_t foo( int *p ) { return *p; }
変化はどれほど微妙ですか?-ポインタの値との差が区別嘘でその番号で家の:アドレス、および内容の値。ポインタが逆参照されるまで問題は発生しません。リンク先のアドレスへのアクセスが試行されるまで。道路を越えて荷物を配達または受け取りしようとするとき...
拡大すると、同じ原則が必然的に前述の必要な有効性を確立する必要性を含む、より複雑な例にも適用されます。
int* validate( int *p, int *head, int *tail ) {
return p >= head && p <= tail ? p : NULL;
}
関係比較と算術は、等価性のテストと同じユーティリティを提供し、原則として同等に有効です。しかし、そのような計算の結果が何を意味するかは、まったく別の問題であり、正確には、あなたが含めた引用によって対処される問題です。
Cでは、配列は連続したバッファーであり、連続した線形の一連のメモリ位置です。そのような特異なシリーズ内の位置を参照するポインターに適用される比較と算術は、当然ながら、相互に、およびこの「配列」(単純にベースで識別される)の両方に関して明らかに意味があります。まったく同じことがmalloc
、またはを介して割り当てられたすべてのブロックに適用されますsbrk
。ので、これらの関係がある暗黙の、コンパイラは、それらの間の有効な関係を確立することができる、ことができるので自信を持って計算が予想される答えを提供すること。
参照ポインタに類似の体操を行う別個のブロックまたはアレイは、任意のそのような提供していない固有の、及び見かけのユーティリティを。なぜなら、ある時点で存在する関係は、その後に続く再割り当てによって無効にされる可能性があるためです。この再割り当ては、変更される可能性が非常に高いため、反転されます。このような場合、コンパイラは、以前の状況での信頼を確立するために必要な情報を取得できません。
ただし、プログラマーとして、あなたはそのような知識を持っているかもしれません!そして、場合によってはそれを利用する義務があります。
そこAREような状況のため、これさえ完全でVALIDと完璧PROPERは。
実際、まさにmalloc
それは、再利用されたブロックをマージしようとする時が来たときに、それ自体が内部的にしなければならないことです-大部分のアーキテクチャで。オペレーティングシステムアロケータについても同様ですsbrk
。もし、より明らかに、頻繁に、上の複数の異なる複数のエンティティ、批判的にも、関連する、これはプラットフォーム上で- malloc
ないかもしれません。また、Cで書かれていないものはどれくらいありますか?
アクションの有効性、安全性、および成功は、必然的にそれが前提とされ、適用される洞察のレベルの結果です。
あなたが提供した引用の中で、カーニハンとリッチーは密接に関連しているが、それでも別個の問題に取り組んでいます。それらは言語の制限を定義し、コンパイラーの機能を利用して、少なくとも誤った構造を検出することによってユーザーを保護する方法を説明しています。それらは、プログラミングタスクを支援するために、メカニズムが可能な長さ、つまり設計された長さを説明しています。コンパイラはあなたのしもべであり、あなたはマスターです。ただし、賢明なマスターは、彼のさまざまな使用人の能力に精通しているマスターです。
この文脈では、未定義の動作は潜在的な危険と危害の可能性を示すのに役立ちます。私たちが知っているように、差し迫った、不可逆的な運命、または世界の終わりを意味するものではありません。それは単にことを意味し、私たち「コンパイラの意味は、」 - - このことはあるかもしれないものについての推測をする、または表すことができないと、このような理由のために、我々は問題の私たちの手を洗うことを選択します。この施設の使用または誤用に起因する可能性のあるいかなる冒険についても、当社は責任を負いません。
実際、それは単に「このポイントを超えて、カウボーイ:あなたはあなた自身である...」と単に言います。
あなたの教授はあなたにもっと細かいニュアンスを示すことを求めています。
彼らが彼らの例を作る際にどれほど細心の注意を払ったかに注目してください。そしてどのようにもろく、それはまだです。のアドレスを取ることによりa
、
p[0].p0 = &a;
コンパイラーは、変数をレジスターに配置するのではなく、変数に実際のストレージを割り振るように強制されます。これは自動変数なので、プログラマーはそれがどこに割り当てられているかを制御できません。そのため、それに続くものについて有効な推測をすることができません。これが、コードが期待どおりに機能するためにゼロに設定する必要がある理由です。a
この行を変更するだけです:
char a = 0;
これに:
char a = 1; // or ANY other value than 0
プログラムの動作が未定義になります。少なくとも、最初の答えは1になります。しかし、問題ははるかに不吉です。
現在、コードは災害を招いています。
それでもながら完全に有効としても、標準に準拠し、それは今ある病気-形成し、コンパイルしてくださいが、様々な理由で実行に失敗することがあります。今のところ存在しない複数の問題- どれもそのコンパイラがあることして認識しています。
strcpy
のアドレスから開始し、a
これを超えて、nullが検出されるまで、バイトごとに消費し、転送します。
p1
ポインタは、まさにのブロックに初期化されている10バイト。
場合a
、ブロックの端部に配置されるように起こり、プロセスは次のものへのアクセス何を持っていない、非常に次のリード- P0の[1] -セグメンテーション違反を誘発します。このシナリオはx86アーキテクチャではありそうもありませんが、可能です。
のアドレスを超えた領域a
がアクセス可能であれば、読み取りエラーは発生しませんが、プログラムは依然として不幸から救われていません。
場合はゼロバイトが起こるのアドレスで始まる10内で発生しa
、それがあり、その後のために、まだ、生き残るstrcpy
停止し、少なくとも我々は、書き込み違反を被ることはありません。
ミスによる読み取りに障害はないが、この10のスパンでゼロバイトが発生しない場合、strcpy
は続行し、によって割り当てられたブロックを超えて書き込みを試みmalloc
ます。
これが、ポインタに関連するバグの追跡が非常に困難になる理由です。これらの行が、他の誰かが書いた数千行の複雑に関連するコードの行の奥深くに埋め込まれていて、あなたが徹底的に調査するように指示されていると想像してください。
それにもかかわらず、完全に有効で標準に準拠した Cであり続けるため、プログラムはコンパイルする必要があります。
エラーこれらの種類は、何の標準および無コンパイラは不用心に対してを守ることはできません。それがまさに彼らがあなたに教えようとしていることだと思います。
偏執狂的な人々は絶えずC の性質を変えてこれらの問題のある可能性を捨て、私たちを私たち自身から救おうと努めています。しかし、それは不誠実です。これは、力を追求し、機械をより直接的かつ包括的に制御することで得られる自由を得ることを選択した場合に受け入れる義務がある責任です。パフォーマンスの完璧さを追求するプロモーターと追求者は、これ以上何も受け入れません。
移植性とそれが表す一般性は根本的に別の考慮事項であり、規格が対処しようとしているすべてのことです。
このドキュメントは、フォームを指定し、プログラミング言語Cで表現されたプログラムの解釈を確立します。 目的は、さまざまなコンピューティングシステムでのC言語プログラムの移植性、信頼性、保守性、および効率的な実行を促進することです。
これが、定義と区別しておくことが完全に適切な理由であり、技術仕様言語そのものの。多くの人が信じているように見えることに反して一般がある対極に異例と模範的な。
結論として:
- ポインタ自体の検査と操作は常に有効であり、多くの場合実りの多いものです。結果の解釈は、意味がある場合とない場合がありますが、ポインタが逆参照されるまで、災難は招かれません。リンクされたアドレスへのアクセスが試行されるまで。
これが真実でなければ、私たちが知っている(そしてそれを愛する)プログラミングは不可能だったでしょう。
C
あるものと安全でC
。同じ型への 2つのポインタの比較は常に可能です(たとえば、等価性のチェック)。ただし、ポインタ演算と比較>
を使用して、特定の配列(またはメモリブロック)内で使用する場合に<
のみ安全です。