`char *array[size]`和`extern char **array`的链接存在问题吗?

6

首先,看一下这个例子(我为了举例编写的,不是真正的程序):

whatever.h

#ifndef WHATEVER_H
#define WHATEVER_H

void fill(void);

#endif

main.c

#include <stdio.h>
#include "whatever.h"

char *names[10] = {NULL};

int main()
{       
        int i; 
        fill(); 
        for (i = 0; i < 10; ++i)
                printf("%s\n", names[i]);
        return 0;
}

whatever.c

#include "whatever.h"

extern char **names;

void fill(void)
{
        int i;
        for (i = 0; i < 10; ++i)
                names[i] = "some name";
}

当我使用以下方式编写此程序时:

gcc -o test main.c whatever.c -Wall -g

我没有收到任何错误或警告。但是,当我运行程序时,我发现在fill函数中,names实际上是NULL。如果我在whatever.c文件中做出更改,问题就会解决。

extern char **names;

to

extern char *names[];

那么一切都很好。

有人能解释一下为什么会发生这种情况吗?如果gcc无法将extern char **names;main.c中的一个链接起来,它不应该给我一个错误吗?如果它可以链接它们,为什么names最终在whatever.c中变成了NULL

此外,extern char **names;extern char *names[];有什么区别?


我正在使用Linux下的gcc版本4.5.1。

更新

为了进一步调查这个问题,我改变了main.cnames的定义:

char *names[10] = {"1", "2", "3", "4", "5", "6", "7", "8", "9", "10"};

(在whatever.c中保留extern char **names;)并且使用gdb,我可以看到names有一个值。如果我将该值转换为char *并打印它,它会给我"1"。(请注意,不是*names"1",而是(char *)names
基本上,这意味着gcc已经成功地将whatever.c中的extern char **names;main.c中的names[0]链接起来了!

有人可以用其他编译器(或更新版本的gcc)测试一下吗? - Shahbaz
@RichardJ.RossIII,我刚意识到(并检查了一下)链接器不会检查类型兼容性,现在想想这完全是有道理的。 - Shahbaz
@Bo:不是重复问题。这个问题是关于指针数组和指向指针的指针的区别,而你链接的那个问题是关于指向数组的指针和指向指针的指针的区别。 - Adam Rosenfield
@BoPersson,谢谢你提供的链接。它澄清了我在过去8年中一直存在的错误认识。 - Shahbaz
1个回答

5
每当你在不同的编译单元中使用不兼容类型来表示相同的变量时(就像你在这里做的一样),你就会得到未定义行为。这意味着它可能无法正常工作,而且你可能不会收到任何错误或警告消息。
为什么会发生这种情况(以及为什么规范说这是未定义的)是因为大多数链接器的工作方式。大多数链接器并不了解类型;它们只了解名称和内存。因此,对于链接器来说,一个变量只是从某个地址开始的一块内存。在你(原始)的程序中,main.c将names定义为引用足够大以容纳10个指针的内存块(可能是40或80字节,具体取决于系统是32位还是64位),所有这些指针都为NULL。但另一方面,whaterver.c假设names引用的是足够大以容纳一个指针的内存块,而该指针指向一个包含10个指针的数组。

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