一个extern C指针益智游戏

时间:2015-01-26 23:18:38

标签: c pointers linker

您将获得以下两个C文件:

#include <stdint.h>
#include <stdio.h>

extern uint32_t *foo;

int main() {
    printf("%p\n", foo);
    printf("%x\n", *foo);
}

#include <stdint.h>
uint32_t foo[2] = {0xDEADBEEF, 0xCAFEFEED};

假设您正在x86_64处理器上运行,那么当您将这两个文件编译并链接在一起时会发生什么?更重要的是,为什么?

3 个答案:

答案 0 :(得分:3)

益智游戏有单独的“堆叠”网站:https://codegolf.stackexchange.com/

在你的情况下,你是骗你的编译器。您可以将'foo'定义为第二个文件中数组的名称,并将其定义为“main”文件中的指针。数组和指针是不同的概念。

如果您将main中的extern声明更改为与第二个模块中的相同,您将可以: extern uint32_t foo [];

补充:如果你“内联”foo并替换     extern uint32_t * foo; 同     uint32_t foo [2] = {0xDEADBEEF,0xCAFEFEED}; 然后编译器将看到您的变量不是指针而是数组的名称。就像你做extern unit32_t foo []时一样。例如,请检查:Is an array name a pointer?

答案 1 :(得分:2)

你会得到

DEADBEEFCAFEFEED
Segmentation fault

因为C数组是直接存储的,所以没有中间引用或指向它们的指针。我想你会发生这种情况:

  1. C编译器将两个DEAD编号放在一起(消耗8个字节)。
  2. C编译器创建一个指向该内存区域的指针并将其命名为 foo (消耗额外的8个字节)。
  3. 链接器稍后在主文件中使用该指针 foo
  4. 第2步很容易让人期待,因为在很多情况下,数组就像指针一样,即:

    int a[2] = {1, 3};
    ...
    *a
    

    但它们是不是指针,它只是C编译器通过说*a知道你的意思。您可以参考:

    进行检查
    int a[2] = {1, 2};  
    printf("%p %p\n", a, &a);  /* Prints same values */
    

    所以这里真的发生了什么:

    1. C编译器将两个DEAD编号放在一起,并将它们称为 foo
    2. 链接器稍后在主文件中使用该指针 foo 。但链接器不知道 foo 是否是一个数组,因此它将其视为传统的8字节指针。

答案 2 :(得分:1)

正如other answer指出的那样,你向编译器谎称foo的类型。因此,您的程序具有未定义的行为,在这种情况下会导致分段错误。

将变量声明为extern时,您应该永远直接将extern语句放入.c文件中。您应该始终extern语句放入头文件中,然后#include在需要它的任何.c文件中添加该标头。但最重要的是,您应该始终在定义变量的.c文件中包含该标头,以便编译器可以针对变量定义验证extern声明。

所以代码应该由下面显示的三个文件组成

foo.h中

#include <stdint.h>
extern uint32_t *foo;

foo.c的

#include <stdint.h>
#include "foo.h"
uint32_t foo[2] = {0xDEADBEEF, 0xCAFEFEED};

的main.c

#include <stdio.h>
#include <stdint.h>
#include "foo.h"

int main( void )
{
    printf("%p\n", (void *)foo);
    printf("%x\n", *foo);
}

在这种情况下,您从gcc收到的错误消息是

  

foo.c:3:错误:'foo'的冲突类型

     

foo.h:2:错误:先前'foo'的声明在这里

当然,您需要通过修复foo.h

来修复错误

foo.h中

#include <stdint.h>
extern uint32_t foo[];