これは災害につながります:
extern LS_Led** LS_vol_leds;
代わりにこれを試してください:
extern LS_Led *LS_vol_leds[];
その理由を本当に知りたい場合は、Peter Van Der Linden によるExpert C Programming - Deep C Secrets (すばらしい本!) の特に第 4 章を読む必要がありますが、簡単な答えは、これはポインタと配列は交換可能ではありません。ポインターは別の配列のアドレスを保持する変数ですが、配列名はアドレスです。extern LS_Led** LS_vol_leds;
コンパイラに嘘をつき、アクセスするための間違ったコードを生成していますLS_vol_leds[i]
。
これとともに:
extern LS_Led** LS_vol_leds;
コンパイラはLS_vol_leds
をポインタと見なすため、LS_vol_leds[i]
を担当するメモリ位置に格納されている値を読み取り、それをアドレスとしてLS_vol_leds
使用し、それに応じてスケーリングi
してオフセットを取得します。
ただし、LS_vol_leds
はポインターではなく配列であるため、コンパイラーは代わりに のアドレスをLS_vol_leds
直接選択する必要があります。言い換えれば、何が起こっているかというと、元のオブジェクトが指すオブジェクトのアドレスを保持していると信じているためextern
、コンパイラが逆参照するということです。LS_vol_leds[0]
LS_vol_leds[0]
更新: おもしろい事実 - 本の裏表紙には、この特定のケースについて書かれています。
そのため、extern char *cp は extern char cp[] と同じではありません。表面的には同等であるにもかかわらず、機能しないことはわかっていましたが、その理由はわかりませんでした. [...]
UPDATE2 : わかりました、あなたが尋ねたので、さらに掘り下げましょう。プログラムが 2 つのファイルに分割されているfile1.c
としfile2.c
ます。その内容は次のとおりです。
file1.c
#define BUFFER_SIZE 1024
char cp[BUFFER_SIZE];
/* Lots of code using cp[i] */
file2.c
extern char *cp;
/* Code using cp[i] */
file2.cに代入cp[i]
または使用しようとすると、コードがクラッシュする可能性が高くなります。これは、C の仕組みと、コンパイラが配列ベースのアクセスとポインターベースのアクセス用に生成するコードに深く関わっています。cp[i]
ポインターがある場合は、それを変数と考える必要があります。ポインタは のような変数ですがint
、float
整数や浮動小数点数を格納する代わりに、別のオブジェクトのアドレスであるメモリ アドレスを格納します。
変数にはアドレスがあることに注意してください。次のような場合:
int a;
a
次に、それが整数オブジェクトの名前であることがわかります。に割り当てるとa
、コンパイラは に関連付けられているアドレスに書き込むコードを発行しますa
。
今あなたが持っていると考えてください:
char *p;
にアクセスするとどうなります*p
か? 覚えておいてください - ポインタは変数です。これは、 に関連付けられたメモリ アドレスp
がアドレス、つまり文字を保持するアドレスを保持することを意味します。代入するとp
(つまり、別の場所を指すようにする)、コンパイラは のアドレスを取得しp
、新しいアドレス (指定したアドレス) をその場所に書き込みます。
たとえば、p
が 0x27 にある場合、メモリ位置 0x27 を読み取ると、 が指すオブジェクトのアドレスが得られることを意味しますp
。したがって、*p
代入の右側で を使用する場合、値を取得する手順は次の*p
とおりです。
- 0x27 の内容を読み取ります - 0x80 とします - これはポインタの値、または同等に、ポイント先のオブジェクトのアドレスです
- 0x80 の内容を読み取ります。これにより、最終的に
*p
.
p
配列の場合はどうなりますか? が配列の場合p
、変数p
自体が配列を表します。慣例により、配列を表すアドレスはその最初の要素のアドレスです。コンパイラが配列をアドレス 0x59 に格納することを選択した場合、これは の最初の要素が 0x59 に存在することを意味しp
ます。したがって、p[0]
(または*p
) を読み取ると、生成されるコードはより単純になります。コンパイラは、変数p
が配列であることを認識し、配列のアドレスは最初の要素のアドレスであるため、p[0]
0x59 を読み取るのと同じです。これをp
がポインターの場合と比較してください。
コンパイラーに嘘をつき、配列ではなくポインターを持っていると言った場合、コンパイラーは (誤って) ポインターの場合に示したコードを生成します。基本的に、0x59 は配列のアドレスではなく、ポインターのアドレスであることを伝えています。したがって、読み取るp[i]
と、ポインターバージョンが使用されます。
- 0x59 の内容を読み取ります。実際には、これは
p[0]
- それをアドレスとして使用し、その内容を読み取ります。
そのため、コンパイラはそれp[0]
をアドレスと見なし、それをアドレスとして使用しようとします。
なぜこれがコーナーケースなのですか?配列を関数に渡すときに、これについて心配する必要がないのはなぜですか?
実際に起こっているのは、コンパイラがそれを管理しているためです。はい、配列を関数に渡すと、最初の要素へのポインターが渡され、呼び出された関数内では、それが「実際の」配列かポインターかを知る方法がありません。ただし、関数に渡されるアドレスは、実際の配列を渡すかポインターを渡すかによって異なります。実数配列を渡す場合、取得するポインターは配列の最初の要素のアドレスです (つまり、コンパイラーはシンボル テーブルから配列変数に関連付けられたアドレスをすぐに取得します)。ポインターを渡す場合、コンパイラーは、その変数に関連付けられたアドレスに格納されているアドレスを渡します (そして、その変数はたまたまポインターです)。つまり、ポインターベースのアクセスについては、前述の 2 つの手順を正確に実行します。繰り返しますが、私たちは議論していることに注意してくださいここのポインタの値。これは、ポインター自体のアドレス (ポイント先のオブジェクトのアドレスが格納されているアドレス) とは別にしておく必要があります。
そのため、違いがわかりません。ほとんどの場合、配列は関数の引数として渡されますが、これによって問題が発生することはめったにありません。しかし、時々、いくつかのコーナーケース (あなたのケースのような) で、そこで何が起こっているのか本当にわからない場合、まあ.. それはワイルドな乗り物になるでしょう.
個人的なアドバイス:本を読んでください。それだけの価値があります。