V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
• 请不要在回答技术问题时复制粘贴 AI 生成的内容
emonber
V2EX  ›  程序员

问个 C 语言的问题

  •  
  •   emonber · 2018-09-26 11:02:17 +08:00 · 2209 次点击
    这是一个创建于 2279 天前的主题,其中的信息可能已经有所发展或是发生改变。

    a.c文件中定义了数组int a[100],在b.c里用extern int *a定义,然后在两个文件里分别打印&aa,前者的打印结果一致,后者打印结果不一样:

    a.c: &a 0x601060, a 0x601060.
    b.c: &a 0x601060, a (nil).
    

    a.c代码:

    int a[100];
    
    void func_a()
    {
            printf("%s: &a %p, a %p.\n", __FILE__, &a, a);
    }
    

    b.c代码:

    extern int *a;
    extern void func_a();
    
    int main(void)
    {
            func_a();
            printf("%s: &a %p, a %p.\n", __FILE__, &a, a);
            return 0;
    }
    
    19 条回复    2018-09-26 14:36:14 +08:00
    glacer
        1
    glacer  
       2018-09-26 11:07:22 +08:00   ❤️ 1
    你声明的是 int 指针而不是 int 数组,这不等价的。
    正确声明方式是 extern int a[]
    emonber
        2
    emonber  
    OP
       2018-09-26 11:08:25 +08:00
    @glacer 那为什么`&a`的地址是一样的呢?
    Andiry
        3
    Andiry  
       2018-09-26 11:09:35 +08:00
    这是一个典型的“数组就是指针”的误解
    wizardoz
        4
    wizardoz  
       2018-09-26 11:09:53 +08:00
    第一个数数组,第二个是指针,怎么感觉是未定义的行为啊,编译器把指针 extern 到数组指针了。
    如果我的话会在 b.c 里面
    extern int a[100];
    没必要关心这些隐晦的行为。
    实际上我写了几年 C,都没怎么用过 extern 变量的情况。
    wizardoz
        5
    wizardoz  
       2018-09-26 11:11:34 +08:00
    extern 函数对于框架代码的实现很有用。
    extern 变量则很容易变成全局变量的滥用,完全可以在设计上避免 extern 变量。
    pkokp8
        6
    pkokp8  
       2018-09-26 11:15:22 +08:00 via Android   ❤️ 1
    a 文件将变量 a 当作数组解释,因此 a 地址为 a 数组首地址的地址,a 为数组 a 的首地址
    b 文件将 a 当作指针解释,因此 a 地址为指针 a 的地址,同数组解释。a 为指针的值
    你在 a 文件中给数组赋初值试试
    别写这种代码,历史代码就改掉
    emonber
        7
    emonber  
    OP
       2018-09-26 11:16:09 +08:00
    @wizardoz 感谢回复,我最近在多核实时操作系统下开发,用 extern 可以容易实现核间的变量共享。
    我理解了 int a[]和 int *a 是两个类型,但是不理解为什么&a 的值是一样的。
    emonber
        8
    emonber  
    OP
       2018-09-26 11:20:10 +08:00
    @pkokp8 感谢解答。所以&a 值一样是编译器实现的原因(我用 gcc 和 clang 的结果都是一样的)?

    在 a.c 里定义 int a[100] = {1};后,打印结果如下:

    ```bash
    a.c: &a 0x601030, a 0x601030.
    b.c: &a 0x601030, a 0x1.
    ```
    wizardoz
        9
    wizardoz  
       2018-09-26 11:20:35 +08:00   ❤️ 1
    @emonber 我的理解是这样,extern 并不区分变量类型,而只是区分变量名。所以数组 a 和指针 a 放在了同一内存区域。
    这就是&a 一样的原因
    而 a 也是一样,只不过 a.c 的 a 是数组,所以打印出来就是它的实际内存地址。而 b.c 的 a 是变量,它所在的区域的值就是 null

    你可以试试 a.c 中 a[0] = 1,然后在打印 b.c 中的 a,看看是否为 1.
    bp0
        10
    bp0  
       2018-09-26 11:20:55 +08:00   ❤️ 2
    extern int *a; 在 b.c 中声明了一个弱符号。连接的时候连接器使用了 a.c 中的 a 进行了替换。

    另外,全局变量未初始化时,会被放在 bss 段,也就是说被默认初始化成 0.


    然后在 b.c 中使用&a,就是 a.c 中数组 a 的地址,因此相同。

    但是在 b.c 中使用 a,是需要按照指针的方式进行的。也就是说用 a 中的值作为指针。因为 a.c 中 a 数组被初始化成 0。所以这个地方打印出(nil)。


    总结一下,指针是要额外占用一个空间保存指向的地址的。而数组名称并不占用额外的空间,直接表示数组首地址。
    innoink
        11
    innoink  
       2018-09-26 11:22:09 +08:00   ❤️ 2
    extern int* a
    这句话,编译器在编译 b.c 时,认为在 a.c 中的符号 a 是一个 int*变量,即符号 a 所在的内存后面 4 字节(32 位)或 8 字节(64 位)里面存放的内容是一个地址,因为全局变量给你初始化成 0 了,所以就是个 nil
    但是,实际在 a.c 中的符号 a 其实是 int [100],a 是一个数组名,数组名取地址和不取地址是一样的,都是首地址,因此 a.c 中,a 和&a 是一样的

    int* a 是指,有个变量 a,它指向一块连续内存,注意 a 本身也有内存
    int a[100],这里的 a 可以理解为一个字面量,本身不占内存
    所以 b.c 中,编译器试图在 a.c 中找 a 所占的内存,即会认为 a.c 中的符号 a 所在的内存后面 4 字节(32 位)或 8 字节(64 位)就是一个 int* 变量,而实际没有这么个变量,当然就错了
    emonber
        12
    emonber  
    OP
       2018-09-26 11:25:13 +08:00
    @wizardoz 感谢~
    @bp0 感谢~解释很清晰。
    bp0
        13
    bp0  
       2018-09-26 11:40:54 +08:00
    @innoink extern 的变量只是声明,并不是定义,所以不会被分配空间。如果最后连接时找不到 a.c 中的数组 a,那么连接会失败。
    fcten
        14
    fcten  
       2018-09-26 11:41:20 +08:00   ❤️ 1
    ```
    #include <stdio.h>

    int *a;

    int main(void)
    {
    printf("%s: &a %p, a %p.\n", __FILE__, &a, a);
    return 0;
    }
    ```

    400559: 48 8b 05 00 0b 20 00 mov 0x200b00(%rip),%rax # 601060 <a>

    ```
    #include <stdio.h>

    int a[100];

    int main(void)
    {
    printf("%s: &a %p, a %p.\n", __FILE__, &a, a);
    return 0;
    }
    ```

    40052a: b9 60 10 60 00 mov $0x601060,%ecx

    数组是数组,指针是指针。
    chiu
        15
    chiu  
       2018-09-26 11:45:22 +08:00 via Android   ❤️ 1
    个人愚见:
    如果 a 本身是一个数组,那么 a 等于&a,就像函数名本身是指向这个函数的指针,对函数名取址&func 也是相同的值。
    如果 a 是一个指针,那么对 a 取址&a 的值就是存放这个指针变量的地址。
    innoink
        16
    innoink  
       2018-09-26 11:49:40 +08:00
    @bp0 是这样的,所以我说的有问题吗
    bp0
        17
    bp0  
       2018-09-26 13:08:14 +08:00   ❤️ 1
    @innoink 不好意思,上面的回复 @错人了,应该 @wizardoz

    你的回复有个不严谨的地方是,编译 b.c 的时候编译器并不知道 a 在什么地方,也就是说不知道 a 在 a.c 中。所以只能按照声明的类型进行编译。如果编译器知道 a 的位置,那么就能检查出类型不对,直接编译报错了。
    XiaoxiaoPu
        18
    XiaoxiaoPu  
       2018-09-26 13:28:19 +08:00
    @chiu a 不等于 &a,类型不一样,差距大了
    chiu
        19
    chiu  
       2018-09-26 14:36:14 +08:00 via Android
    @XiaoxiaoPu 嗯,我表述不严谨,我指值相等,既楼主所说的前者打印一样。
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   981 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 22ms · UTC 19:06 · PVG 03:06 · LAX 11:06 · JFK 14:06
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.