0

我有一个

LS_Led* LS_vol_leds[10];

在一个 C 模块中声明,并在访问它的其他模块中声明适当的外部。

func1()我有这一行:

/* Debug */
LS_Led led = *(LS_vol_leds[0]);

不会导致异常。然后我调用func2()另一个 C 模块(就在上面的行之后),并执行相同的行,即:

/* Debug */
LS_Led led = *(LS_vol_leds[0]);`

首先,抛出异常!!!

我不认为我有能力自己调试这个。

LS_vol_leds初始化任何东西之前func1()

LS_vol_leds[0] = &led3;
LS_vol_leds[1] = &led4;
LS_vol_leds[2] = &led5;
LS_vol_leds[3] = &led6;
LS_vol_leds[4] = &led7;
LS_vol_leds[5] = &led8;
LS_vol_leds[6] = &led9;
LS_vol_leds[7] = &led10;
LS_vol_leds[8] = &led11;
LS_vol_leds[9] = &led12;

我的外星人看起来像

extern LS_Led** LS_vol_leds;

那么这会导致灾难吗?我该如何预防灾难?

谢谢。

4

1 回答 1

2

这会导致灾难:

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:好的,既然你问了,让我们深入挖掘。考虑一个程序分成两个文件,file1.c并且file2.c. 它的内容是:

文件1.c

#define BUFFER_SIZE 1024
char cp[BUFFER_SIZE];
/* Lots of code using cp[i] */

文件2.c

extern char *cp;
/* Code using cp[i] */

您尝试在file2.ccp[i]中使用或使用的那一刻很可能会使您的代码崩溃。这与 C 的机制以及编译器为基于数组的访问和基于指针的访问生成的代码紧密相关。cp[i]

当你有一个指针时,你必须把它当作一个变量。指针是一个类似于 的变量intfloat或类似的东西,但它不是存储整数或浮点数,而是存储内存地址 - 另一个对象的地址。

请注意,变量具有地址。当你有类似的东西时:

int a;

然后你知道这a是一个整数对象的名称。当您分配给 时a,编译器会发出写入与 关联的任何地址的代码a

现在考虑你有:

char *p;

访问时会发生什么*p?请记住——指针是一个变量。这意味着与相关联的内存地址p保存一个地址——即保存一个字符的地址。当您分配给p(即,使其指向其他地方)时,编译器会获取地址p并将新地址(您提供的地址)写入该位置。

例如,如果p位于 0x27,则意味着读取内存位置 0x27 会产生 指向的对象的地址p。因此,如果您*p在赋值的右侧使用,获取值的步骤*p是:

  1. 读取 0x27 的内容 - 假设它是 0x80 - 这是指针的值,或者等效地,指向对象的地址
  2. 阅读 0x80 的内容——这终于给了你*p

如果p是数组呢?如果p是数组,则变量p本身代表数组。按照惯例,表示数组的地址是其第一个元素的地址。如果编译器选择将数组存储在地址 0x59 中,则意味着第一个元素p位于 0x59 处。所以当你读p[0](或*p)时,生成的代码就更简单了:编译器知道变量p是一个数组,而数组的地址就是第一个元素的地址,所以p[0]和读0x59是一样的。p将此与指针的情况进行比较。

如果你对编译器撒谎,并告诉它你有一个指针而不是一个数组,编译器将(错误地)生成代码,这些代码执行我为指针案例展示的代码。您基本上是在告诉它 0x59 不是数组的地址,而是指针的地址。因此,阅读p[i]将导致它使用指针版本:

  1. 阅读 0x59 的内容 - 请注意,实际上,这是p[0]
  2. 用作地址,并读取其内容。

所以,发生的事情是编译器认为这p[0]是一个地址,并会尝试这样使用它。

为什么这是一个角落案例?为什么在将数组传递给函数时我不必担心这个?

因为真正发生的是编译器为您管理它。是的,当您将数组传递给函数时,会传递指向第一个元素的指针,并且在被调用函数内部,您无法知道它是“真实”数组还是指针。但是,传递给函数的地址会有所不同,具体取决于您传递的是真正的数组还是指针。如果你传递一个真正的数组,你得到的指针是数组的第一个元素的地址(换句话说:编译器立即从符号表中获取与数组变量关联的地址)。如果您要传递一个指针,编译器会传递存储在与该变量关联的地址中的地址(并且该变量恰好是指针),即 对于基于指针的访问,它正是前面提到的这两个步骤。再次注意,我们正在讨论这里的指针值。您必须将其与指针本身的地址(存储指向对象的地址的地址)分开。

这就是为什么你看不出有什么不同。在大多数情况下,数组作为函数参数传递,这很少引起问题。但有时,对于一些极端情况(比如你的情况),如果你真的不知道那里发生了什么,那么……那将是一场疯狂的旅程。

个人建议:读这本书,完全值得。

于 2014-05-02T21:49:25.923 回答