web-dev-qa-db-ja.com

Cでポインターをキャストするためのルールは何ですか?

K&Rはそれを越えませんが、彼らはそれを使用します。サンプルプログラムを作成して、どのように機能するかを試してみましたが、うまくいきませんでした。

#include <stdio.h> 
int bleh (int *); 

int main(){
    char c = '5'; 
    char *d = &c;

    bleh((int *)d); 
    return 0;  
}

int bleh(int *n){
    printf("%d bleh\n", *n); 
    return *n; 
}

コンパイルはされますが、私のprintステートメントはガベージ変数を吐き出します(プログラムを呼び出すたびに異なります)。何か案は?

60
Theo Chronic

ポインタについて考えるとき、ダイアグラムを描画するのに役立ちます。ポインターは、メモリ内のアドレスを指す矢印であり、値のタイプを示すラベルが付いています。アドレスはどこを見るかを示し、タイプは何をするかを示します。ポインターをキャストすると、矢印のラベルが変更されますが、矢印が指す場所は変更されません。

dmainは、c型のcharへのポインターです。 charは1バイトのメモリです。したがって、dが間接参照されると、その1バイトのメモリの値が取得されます。次の図では、各セルは1バイトを表しています。

-+----+----+----+----+----+----+-
 |    | c  |    |    |    |    | 
-+----+----+----+----+----+----+-
       ^~~~
       | char
       d

dint*にキャストすると、dは実際にintの値を指していると言います。現在のほとんどのシステムでは、intは4バイトを占有します。

-+----+----+----+----+----+----+-
 |    | c  | ?₁ | ?₂ | ?₃ |    | 
-+----+----+----+----+----+----+-
       ^~~~~~~~~~~~~~~~~~~
       | int
       (int*)d

(int*)dを間接参照すると、これらの4バイトのメモリから決定される値を取得します。取得する値は、?とマークされたこれらのセルの内容と、メモリでintがどのように表されるかによって異なります。

PCは リトルエンディアン です。これは、intの値が次のように計算されることを意味します(4バイトに及ぶと仮定):* ((int*)d) == c + ?₁ * 2⁸ + ?₂ * 2¹⁶ + ?₃ * 2²⁴。そのため、値はガベージですが、16進数(printf("%x\n", *n))で印刷すると、最後の2桁は常に35(文字'5'の値になります) )。

他のいくつかのシステムはビッグエンディアンであり、バイトを他の方向に配置します:* ((int*)d) == c * 2²⁴ + ?₁ * 2¹⁶ + ?₂ * 2⁸ + ?₃。これらのシステムでは、16進数で印刷すると、値は常にstarts with 35であることがわかります。一部のシステムのサイズはintで、4バイトとは異なります。 intをさまざまな方法で配置するシステムはほとんどありませんが、それらに遭遇することはほとんどありません。

コンパイラとオペレーティングシステムに応じて、プログラムを実行するたびに値が異なる場合や、ソースコードを少しでも微調整すると値は常に同じである場合があります。

一部のシステムでは、int値を4の倍数(または2、または8)のアドレスに保存する必要があります。これは alignment 要件と呼ばれます。 cのアドレスが正しく整列されているかどうかによって、プログラムがクラッシュする可能性があります。

プログラムとは対照的に、int値があり、それへのポインターを取得すると、次のようになります。

int x = 42;
int *p = &x;
-+----+----+----+----+----+----+-
 |    |         x         |    | 
-+----+----+----+----+----+----+-
       ^~~~~~~~~~~~~~~~~~~
       | int
       p

ポインターpは、int値を指します。矢印のラベルはメモリセルの内容を正しく説明しているため、間接参照しても驚くことはありません。

115
Gilles
char c = '5'

char(1バイト)は、スタック上のアドレス0x12345678に割り当てられます。

char *d = &c;

cのアドレスを取得し、dに保存するので、d = 0x12345678です。

int *e = (int*)d;

0x12345678intを指すとコンパイラーに強制しますが、intは1バイトだけではありません(sizeof(char) != sizeof(int))。アーキテクチャまたは他の値に応じて、4バイトまたは8バイトになる場合があります。

そのため、ポインタの値を出力するとき、整数は最初のバイト(cであった)とスタック上にある他の連続したバイトを取得することで考慮されます。

36
Jack

Cでは通常、ポインターのキャストは無効です。いくつかの理由があります。

  1. アライメント。アライメントの考慮事項により、宛先ポインタータイプがソースポインタータイプの値を表すことができない可能性があります。たとえば、int *が本質的に4バイトでアライメントされている場合、char *int *にキャストすると、下位ビットが失われます。

  2. エイリアシング。一般に、オブジェクトの正しい型の左辺値を介した場合を除き、オブジェクトにアクセスすることは禁止されています。いくつかの例外がありますが、それらを十分に理解していない限り、そうしたくありません。エイリアシングは、実際にポインターを逆参照する場合にのみ問題になることに注意してください(*または->演算子を適用するか、逆参照する関数に渡します)。

ポインターをキャストしても大丈夫な主な注目すべきケースは次のとおりです。

  1. 宛先ポインタータイプが文字タイプを指す場合。文字型へのポインターは、任意の型へのポインターを表すことが保証されており、必要に応じて元の型に正常に往復します。 void(void *)へのポインターは、文字型へのポインターとまったく同じです。ただし、逆参照または算術を行うことは許可されず、他のポインター型との間で自動的に変換されます。このため、通常、voidへのポインターは文字型へのポインターよりも望ましいです。

  2. 宛先ポインター型が、最初にポイントされた構造型の初期メンバーと完全に一致するメンバーを持つ構造型へのポインターである場合。これは、Cでのさまざまなオブジェクト指向プログラミング手法に役立ちます。

他のいくつかのあいまいなケースは、言語要件に関しては技術的には問題ありませんが、問題があり回避するのが最善です。

12
R..

charへのポインターがあります。システムが知っているように、そのメモリアドレスには、sizeof(char)スペースにchar値があります。 int*にキャストすると、sizeof(int)のデータを処理するため、charとそれ以降のメモリガベージを整数として出力します。

2
gkovacs90

もっと一般的な答えが必要だと思う:

Cでのポインターのキャストに関するルールはありません!この言語では、コメントなしで他のポインターにポインターをキャストできます。

しかし、問題は次のとおりです。データ変換などは何もありません!キャスト後のデータをシステムが誤って解釈しないようにするのは、ユーザー自身の責任です。これは通常、ランタイムエラーの原因となります。

したがって、キャストされたポインターからデータが使用される場合、データに互換性があることに注意するために完全にあなたにキャストするとき!

Cはパフォーマンスが最適化されているため、ポインター/参照の実行時の再帰性がありません。しかし、それには代償があります-プログラマーとしてのあなたは、あなたがしていることをよりよく気にしなければなりません。あなたがしたいことが「合法」である場合、あなたはあなた自身で知る必要があります

2
Ole Dittmann

ガベージ値は、実際には、宣言の前に関数bleh()を呼び出したためです

C++の場合、コンパイルエラーが発生しますが、cでの場合、コンパイラは関数の戻り値の型がintであると仮定しますが、関数はへのポインタを返します整数。

詳細はこちらをご覧ください: http://www.geeksforgeeks.org/g-fact-95/

1
Saket