#include <stdio.h>
int main()
{
const int a = 12;
int *p;
p = &a;
*p = 70;
}
それは機能しますか?
#include <stdio.h>
int main()
{
const int a = 12;
int *p;
p = &a;
*p = 70;
}
それは機能しますか?
これは「未定義動作」です。つまり、標準に基づいて、これを試したときに何が起こるかを予測することはできません。特定のマシン、コンパイラ、およびプログラムの状態に応じて、異なる処理を実行する場合があります。
この場合、最も頻繁に起こることは、答えが「はい」になるということです。constかどうかに関係なく、変数はメモリ内の単なる場所であり、constnessの規則を破って、単純に上書きすることができます。(もちろん、プログラムの他の部分がそのconstデータが一定であることに依存している場合、これは重大なバグを引き起こします!)
ただし、場合によっては(最も一般的にはconst static
データの場合)、コンパイラはそのような変数をメモリの読み取り専用領域に配置することがあります。たとえば、MSVCは通常、実行可能ファイルの.textセグメントにconst static intsを配置します。これは、オペレーティングシステムに書き込もうとすると、オペレーティングシステムが保護違反をスローし、プログラムがクラッシュすることを意味します。
コンパイラとマシンのその他の組み合わせでは、まったく異なることが発生する可能性があります。確実に予測できることの1つは、このパターンがコードを読まなければならない人を苛立たせることです。
それは未定義の振る舞いです。証拠:
/* program.c */
int main()
{
const int a = 12;
int* p;
p = &a;
*p = 70;
printf("%d\n", a);
return 0;
}
gcc program.c
そしてそれを実行します。出力は70(gcc 4.3)になります
次に、次のようにコンパイルします。
gcc -O2 program.c
そしてそれを実行します。出力は12になります。最適化を行うと、コンパイラはおそらく12をレジスタにロードし、printfのにアクセスする必要があるときに、変更できないことを「認識」しているため、わざわざ再度ロードすることはありません。
const
ポインタを介して修飾されたオブジェクトを変更すると、未定義の動作が呼び出され、その結果が発生します。これは、特定の実装に期待するものである可能性があります。たとえば、前の値が変更されていない場合.text
などです。
それは確かにgccで動作します。しかし、それは気に入らなかった:
test.c:6:警告:割り当てにより、ポインターターゲットタイプから修飾子が破棄されます
しかし、実行時に値は変更されました。私は明白なノーノーを指摘しません...
はい、そのようなコードを使用してそれを行うことができます。ただし、がグローバルの場合、コードは適用されませんa
(gccでコンパイルされたプログラムが私にくれましsegmentation fault
た)。
一般的に言って、最愛のCでは、ほとんどの場合、変更または公開されるべきではないものをハッキングする方法を見つけることができます。ここでのconstは例です。
しかし、貧しい人(おそらく6か月後の私自身)が私たちのコードを維持していることを考えると、私はしばしばそうしないことを選択します。
ここで、ポインタのタイプは、タイプp
の値( =>変数のアドレス)int*
が割り当てられているです。const int*
&a
const int
gccは警告をスローしますが、暗黙的なキャストはconstnessを排除します(これは実装に大きく依存することに注意してください)。
ポインタはとして宣言されていないため、const
このようなポインタを使用して値を変更できます。
ポインタがとして宣言される場合const int* p = &a
、あなたはそれを行うことができなくなります*p = 70
。
このコードには制約違反が含まれています:
const int a = 12;
int *p;
p = &a;
違反した制約はC116.5.16.1/1"単純な割り当て"です。両方のオペランドがポインターである場合、左側が指す型には、右側が指す型のすべての修飾子が必要です。(そして、タイプ、sans qualifiersは互換性がなければなりません)。
したがって、修飾子として&a
持つタイプconst int *
がであるため、制約に違反します。ただし、その修飾子は、のタイプにはconst
表示されません。p
int *
コンパイラは診断を発行する必要があり、実行可能ファイルを生成しない場合があります。プログラムは言語の規則に準拠していないため、実行可能ファイルの動作は完全に定義されていません。
定数変数を指すポインターを使用して、定数変数の値を変更することはできません。このタイプのポインタはと呼ばれPointer to a constant
ます。
と呼ばれる別の概念もありConstant Pointer
ます。つまり、ポインタがメモリの場所を指していると、別の場所を指すようにすることはできません。
悪い、悪い考え。
また、動作はプラットフォームおよび実装に固有です。定数が書き込み不可能なメモリに格納されているプラットフォームで実行している場合、これは明らかに機能しません。
そして、一体なぜあなたはしたいのですか?ソースの定数を更新するか、変数にします。
変数の値を変更する際の問題const
は、コンパイラーがそれが発生することを予期しないことです。このコードを考えてみましょう:
const int a = 12;
int * p = &a;
*p = 70;
printf("%d\n", a);
コンパイラがa
最後のステートメントを読み取るのはなぜですか?コンパイラはそれを認識してa
おり12
、そうであるためconst
、変更されることはありません。したがって、オプティマイザは上記のコードを次のように変換できます。
const int a = 12;
int * p = &a;
*p = 70;
printf("%d\n", 12);
これは奇妙な問題につながる可能性があります。たとえば、コードは最適化されていないデバッグビルドでは期待どおりに機能する可能性がありますが、最適化されたリリースビルドでは失敗します。
実際、優れたオプティマイザはコード全体を次のように変換する可能性があります。
printf("%d\n", 12);
以前の他のすべてのコードは、コンパイラーの目には影響を与えません。効果のないコードを除外しても、プログラム全体に影響はありません。
一方、まともなコンパイラは、コードに欠陥があることを認識し、警告します。
int * p = &a;
実際には間違っています。正解は次のとおりです。
const int * p = &a;
へのポインタでp
はないので、へint
のポインタでconst int
あり、そのように宣言されると、次の行でハードコンパイルエラーが発生します。
警告を取り除くには、次のキャストを行う必要があります。
int * p = (int *)&a;
const
そして、さらに優れたコンパイラーは、このキャストが約束を破ることを認識し、オプティマイザーにa
として扱わないように指示しconst
ます。
ご覧のとおり、コンパイラの品質、機能、および設定によって、最終的に期待できる動作が決まります。これは、同じコードが異なるプラットフォームで、または同じプラットフォームで異なるコンパイラを使用する場合に、異なる動作を示す可能性があることを意味します。
C標準がその場合の動作を定義していれば、すべてのコンパイラがそれを実装する必要があり、標準が何を定義したとしても、実装が難しく、コンパイラを作成したいすべての人に大きな負担をかけます。標準で「これは禁止されています」と記載されていたとしても、すべてのコンパイラは、このルールを適用するために複雑なデータフロー分析を実行する必要があります。したがって、標準はそれを定義していません。これは、const
値を変更できないことを定義しており、とにかく値を変更する方法を見つけた場合、信頼できる動作はありません。
はい、定数変数の値を変更できます。
このコードを試してください:
#include <stdio.h>
int main()
{
const int x=10;
int *p;
p=(int*)&x;
*p=12;
printf("%d",x);
}