『C语言学习笔记』extern真恶心

extern

在C语言中,一个文件中声明的全局变量和函数是默认以"extern"声明的,是外部文件可见的.

int x = 1;

int foo(int var) 
{
    return var + x;
}

// 等价于

extern int x = 1; // 但是这句产生编译器警告,和"extern int x;"引用外部的x非常容易混淆.

extern int foo(int var)
{
    return var + x;
}

一个文件中以"static"声明的变量和函数是外部文件不可见的.

static int x = 1; // 仅仅是本文件范围的变量

static int foo(int var)  // 仅仅是本文件范围的函数
{
    return var + x;    
}

由于"static"是对于外部范围不可见的,所以可以用"static"声明和外部函数同名的函数,但不要在同一范围使用.

static int x = 1;

int bar() 
{
    static int x = 2;
    {
        prinf("%d", x);
        static int x = 3;
        prinf("%d", x);
    }
    return x;
}

上述的三个静态x都会被分配内存,编译器会使用三个不同的标号:

x:
    .long   1
x.1234:
    .long   2
x.1235:
    .long   3

bar:
    ...
    movl  x.1234(%rip), %esi
    ...
    call  printf
    ...
    movl  x.1235(%rip), %esi
    ...
    movl  x.1234(%rip), %eax

虽然三个x在汇编中没有任何的区别,但是C编译器是按照源文件的逻辑来使用变量的:1.先在所在的周期范围查找;2.再在更大的周期范围一步一步向上查找.所以第一个printf打印2,第二个printf打印3,返回值为2.

从外部引用变量和函数.

tool.c

float var = 1000.11;

int func(int a, int b) 
{
    return a + b;
}

=================================================

main.c

extern float var; // 不要是"extern int big_num = 1",这样是声明不是引用.
extern int func(int, int);

float main ()
{
    func(1, 2);
    return var;
}

如果没有"extern int var;".

extern int func(int, int);

float main ()
{
    func(1, 2);
    return var;
}

凭空岀现的var无法通过C编译器.

如果没有"extern int func(int, int);".

extern float var;

float main ()
{
    func(1, 2);
    // 或者 int a = func(1, 2);都一样
    return var;
}

是可以通过C编译器的,因为"func(1, 2);"不需要函数原型就可以编译成:

movl    $2, %esi
movl    $1, %edi
call    func

所以通过C编译器是应该的.但是没有了函数原型的限制func函数多次使用写错了也是可以通过C编译器的.

extern float var;

float main ()
{
    func(1, 2);
    func(1);
    func();
    func(1, 2, 3, 4);
    return var;
}

因为C编译器根本不知道func的原型是不是可变参数.

邪门歪道

交换了var和func能不能通过C编译和汇编?能,因为在链接之前两个文件是不见面的,自己过自己的.能不能通过链接?

tool.c

float var = 1000.11;

int func(int a, int b) 
{
    return a + b;
}

=================================================

main.c

extern float func;
extern int var(int, int);

float main ()
{
    var(1, 2);
    return func;
}

能,因为main.c通过C编译器的得到的汇编文件中只是用func和var符号完成指令:

movl    $2, %esi
movl    $1, %edi
call    var
mov     func(%rip), %xmm0

但是不会分配内存.这样在通过汇编器得到可重定向文件时,使用var和func的地方被写入.rel .data和.rel .text重定向表, 在.symtab符号表中var和func被标记为NOTYPE,因为汇编文件中并没有var和func的主体,只是平平无奇的两个内存地址.

链接器是纯洁的,只做重定向,从不怀疑.链接tool.o和main.o时,查找tool.o符号表中符号名称为var和func符号对应的内存地址去重定向main.o中的var和func,使得main.c中的var函数是tool.c中var变量的地址,func变量是tool.c中func函数的地址.

总结:

  1. extern用来声明定义外部可见的变量或函数,在汇编文件中用.global伪指令标记,在可重定向文件的符号表中用global标记;static用来声明定义外部不可见的变量或函数,在汇编文件中用.local伪指令标记,在可重定向文件的符号表中用local标记;
  2. extern用来引用外部的变量或函数,在汇编文件中只使用变量或函数的符号,但不给符号分配任何内存,在可重定向文件中和符号有关的位置都被写入重定向表.
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。
禁止转载,如需转载请通过简信或评论联系作者。

相关阅读更多精彩内容

友情链接更多精彩内容