引用数组 vs 引用数组指针

14
void check(void* elemAddr){
    char* word = *((char**)elemAddr);
    printf("word is %s\n",word);
}

int main(){
    char array[10] = {'j','o','h','n'};
    char * bla = array;
    check(&bla);
    check(&array);
}

输出:

word is john

RUN FINISHED; Segmentation fault; core dumped;

第一个有效,但第二个无效。我不明白为什么会这样。


我也添加了 '\0' 字符,但这并没有改变任何东西。 - Ioane Sharvadze
3
&array的类型不是char** - BLUEPIXY
7
我看到一个很好的问题,为什么会有负评? - Maroun
1
@BobJarvis 这是一个约束违规(不兼容的类型比较)。 - M.M
@BobJarvis array == &(array[0]) @BobJarvis array == &(array[0]) - Ioane Sharvadze
显示剩余2条评论
5个回答

7
问题在于,当我们执行&array时,我们从char [10]得到了一个char (*)[10],而不是char **
在我们进行实验之前,我要强调的是,当我们将数组作为参数传递给函数时,C实际上将数组转换为指针。大量数据不会被复制。
因此,在C中,int main(int argc, char **argv)int main(int argc, char *argv[])相同。
这使我们能够通过简单的printf打印数组的地址。
让我们做实验:
char array[] = "john";
printf("array:  %p\n", array);
printf("&array: %p\n", &array);

// Output:
array:  0x7fff924eaae0
&array: 0x7fff924eaae0

了解这些之后,让我们深入研究你的代码:
char array[10] = "john";
char *bla = array;
check(&bla);
check(&array);

blachar *类型,&blachar **类型。

然而,arraychar [10]类型,&arraychar (*)[10]类型,而不是char **类型。

所以当你将&array作为参数传递时,char (*)[10]在作为参数传递时就像一个char *一样,如上所述。

因此,**(char **) &bla == 'j'*(char *) &array == 'j'。做一些简单的实验,您就能证明这一点。

并且您正在将void *elemAddr强制转换为char **并尝试进行解引用操作。这只对&bla有效,因为它是char **类型。&array会导致段错误,因为"john"被解释为地址,就像您所做的强制转换一样。


正确的措辞是,当传递给函数时,数组(或数组地址)衰减为指针 - 你对一个简单的指针是正确的 :-). 被调用的函数接收该指针。 - Serge Ballesta

3

对于check(&bla);,你正在发送指向指针的指针

void check(void* elemAddr){
    char* word = *((char**)elemAddr);  // works fine for pointer to pointer
    printf("word is %s\n",word);
}

这个运作良好。

但是,对于check(&array);,你只传递了指针。

void check(void* elemAddr){
    char* word = *((char**)elemAddr);  // This is not working for pointer 

    char* word = *(char (*)[10])(elemAddr);   // Try this for [check(&array);]

    printf("word is %s\n",word);
}

完整代码--

check(array); 的代码:

void check(void* elemAddr){
    char* word = *(char (*)[10])(elemAddr);
    printf("word is %s\n",word);
}

int main() {
   char array[10] = {'j','o','h','n'};
    check((char*)array);
  return 0;
}

check(&bla);的代码:

void check(void* elemAddr){
    char* word = *((char**)elemAddr);
    printf("word is %s\n",word);
}

int main() {
   char array[10] = {'j','o','h','n'};
   char* bla = array;
   check(&bla);
  return 0;
}

char* word = ((char*)elemAddr); // 尝试这个 [check(&array);] 是未定义行为。 - 2501
1
@AerofoilKite 2501 表示,在 C 语言中将 char (*)[10] 转换为 char * 是“未定义行为”。请注意,您不能使用 C 编译器测试未定义行为(因为“未定义”包括“Airfoil Kite 预期的结果”)。其他编译器可能会产生不同的结果。 - Nikolai Ruhe
@MattMcNabb,现在可以了吗? - Shahriar
2
传递数组作为check(array)是可以的,但在函数中你应该有char* word = (char*)elemAddr;或者只需char* word = elemAddr;(在C中,从void*进行转换是可选的)。原因是:当你传递array时,它会衰变成一个类型为char*且值为&array[0]的指针。这就是为什么在函数中需要进行char*的转换。 - interjay
我看到你已经修复了我3个月前指出的旧错误。但正如interjay所提到的,你引入了一个新的错误。 - 2501
显示剩余4条评论

2
C规范指出,array和&array是同一指针地址。
根据C规范,在将数组作为参数传递给函数时使用数组名称会自动将参数转换为指针(重点在于此)。
引用如下规范:

6.3.2.1-4

除非它是sizeof运算符或一元&运算符的操作数,或者是用于初始化数组的字符串字面量,否则具有类型“类型数组”的表达式将被转换为带有类型“指向该数组对象的初始元素的指针”的表达式,并且不是左值。如果数组对象具有寄存器存储类,则行为未定义。

因此,调用func(array)将导致将char[]指针传递到函数中。但对于在数组上使用地址运算符的特殊情况,存在特例。由于数组具有“类型数组”类型,因此它落入规范的“否则”类别中(重点在于此)。
引用如下规范:

6.5.3.2-3

一元&运算符产生它的操作数的地址。如果操作数具有类型“type”,则结果具有类型“指向type的指针”。如果操作数是一元*运算符的结果,则不评估该运算符和&运算符,结果就像两个运算符都被省略一样,除了运算符的约束仍然适用,结果不是左值。类似地,如果操作数是[]运算符的结果,则不评估&运算符或[]隐含的一元*,结果就像&运算符被移除并且[]运算符被更改为加法运算符一样。否则,结果是指向其操作数所指定的对象或函数的指针

因此,调用func(&array)仍将导致单个指针传递到函数中,就像调用func(array)一样,因为array和&array是相同的指针值。
常识会让你认为&array是数组第一个元素的双重指针,因为使用&运算符通常会表现出这种行为。但数组是不同的。因此,当您将传递的数组指针视为数组的双指针进行解引用时,会发生“Segmentation fault”错误。

C规范的答案。谢谢! - Ioane Sharvadze

1
这不是对你问题的直接回答,但它可能会在将来对你有所帮助。
数组不是指针。
  • type arr[10]:

    • 使用sizeof(type)*10字节的内存空间

    • arr&arr的值相同

    • arr指向有效的内存地址,但不能被设置为指向其他内存地址


  • type* ptr = arr:

    • 额外使用了sizeof(type*)字节的空间

    • 通常情况下,ptr&ptr的值是不同的,除非你设置ptr = (type*)&ptr

    • ptr可以被设置为指向有效和无效的内存地址,可以多次进行设置


关于你的问题:&bla != bla == array == &array,因此&bla != &array

-1

一个问题是你的字符数组不一定会以空字符结尾。由于array是在栈上分配的自动变量,它不能保证被清零。所以,即使你初始化了前4个字符,后面的6个字符仍然是未定义的。

然而...

对于你的问题,简单的答案是&bla != &array,所以你的check()函数假设它会在两个不同的地址找到以空字符结尾的字符数组。

以下等式成立:

array == &array    // while not the same types exactly, these are equivalent pointers
array == bla
&array == bla
*bla == array[0]

&bla 永远不会等于你想要的任何东西,因为该语法引用了本地堆栈上 bla 变量的地址,与其值(或指向的内容)无关。

希望这有所帮助。


不是我的DV,但是当你为数组的某些元素指定初始化程序时,其余的元素将被初始化为0。此外,你的方程式,如array == &array可能会让人误解,因为它们是不同的类型。 - interjay
啊,你说得对,关于数组初始化我漏掉了 -- 我一直为了清晰明了而进行显式初始化。谢谢你的提醒。 - alpartis

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接