LinuxSir.cn,穿越时空的Linuxsir!

 找回密码
 注册
搜索
热搜: shell linux mysql
楼主: zqpytrjb

【疑惑】这里究竟有几个会C语言的人?

[复制链接]
发表于 2005-4-24 16:20:13 | 显示全部楼层
发这个帖子的家伙,真该chuai,标准菜鸟!!!!! :beat
回复 支持 反对

使用道具 举报

发表于 2005-4-24 16:42:10 | 显示全部楼层
Post by kj501
static和extern这些东西实际上不是给编译器用的,而是给链接器用的。从链接的角度来看待这些问题,其实是很好理解的。


static, extern这些东西就是给编译器也的,看到static就不把符号写到obj文件中。链接器只管在obj文件中找符号,找不到就链接失败。
回复 支持 反对

使用道具 举报

发表于 2005-4-24 18:36:22 | 显示全部楼层
我认为语言只是工具, 程序员要知道做什么,该怎么做,不能被某种程序的细节限制死,
否则只是个读死书的,

我通常都是在身边放本手册,我通常不关心语言的细节,
回复 支持 反对

使用道具 举报

发表于 2005-4-24 19:48:59 | 显示全部楼层
Post by Tetris
static, extern这些东西就是给编译器也的,看到static就不把符号写到obj文件中。链接器只管在obj文件中找符号,找不到就链接失败。

不对。既使是static的全局变量和函数,编译器也要把它们的符号写到obj文件中。比如说下面的这段代码:

  1. #include <unistd.h>

  2. static int a;

  3. static int b(void)
  4. {
  5. }

  6. int main ()
  7. {
  8.         static int c;

  9. }
复制代码

编译之后,用readelf -s查看可执行文件中的符号,可以看到:

  1.     81: 08048354     5 FUNC    LOCAL  DEFAULT   12 b
  2.     82: 08049568     4 OBJECT  LOCAL  DEFAULT   23 c.0
  3.     83: 0804956c     4 OBJECT  LOCAL  DEFAULT   23 a
复制代码

只不过static的bind类型是LOCAL而已。
链接器在链接时不仅要对符号进行解析,而且还要将各个obj文件中的变量地址和函数地址进行重定位,给出其中生成的可执行文件中的地址。如果编译器不把static的符号导出到obj文件中,链接器如果给这些变量和函数重新定位呢?
回复 支持 反对

使用道具 举报

发表于 2005-4-24 21:23:35 | 显示全部楼层
Post by kj501
不对。既使是static的全局变量和函数,编译器也要把它们的符号写到obj文件中。比如说下面的这段代码:

  1. #include <unistd.h>

  2. static int a;

  3. static int b(void)
  4. {
  5. }

  6. int main ()
  7. {
  8.         static int c;

  9. }
复制代码

编译之后,用readelf -s查看可执行文件中的符号,可以看到:

  1.     81: 08048354     5 FUNC    LOCAL  DEFAULT   12 b
  2.     82: 08049568     4 OBJECT  LOCAL  DEFAULT   23 c.0
  3.     83: 0804956c     4 OBJECT  LOCAL  DEFAULT   23 a
复制代码

只不过static的bind类型是LOCAL而已。
链接器在链接时不仅要对符号进行解析,而且还要将各个obj文件中的变量地址和函数地址进行重定位,给出其中生成的可执行文件中的地址。如果编译器不把static的符号导出到obj文件中,链接器如果给这些变量和函数重新定位呢?


呵呵,我说错了。不过我的意思就是编译器根据static决定符号可不可以被外部引用,但不应该说是不写到obj文件里。所以我觉得static和extern是被编译器使用而不是被链接器使用。特别是编译器还得根据extern语句来决定外部符号的类型。
回复 支持 反对

使用道具 举报

发表于 2005-4-25 10:09:00 | 显示全部楼层
Post by Tetris
不过我的意思就是编译器根据static决定符号可不可以被外部引用,但不应该说是不写到obj文件里。所以我觉得static和extern是被编译器使用而不是被链接器使用。特别是编译器还得根据extern语句来决定外部符号的类型。

我认为你的理解仍然不对。编译器在编译多个源文件时,是以文件为单位,一个文件一个文件的编译的。不是多个文件一起编译。
比如说有下面两个程序tt.c和temp.c:

  1. /* file tt.c */

  2. #include <stdio.h>

  3. int main()
  4. {
  5.         foo();
  6. }
复制代码

  1. /* file temp.c */
  2. static void foo(void)
  3. {
  4. }
复制代码

在tt.c中引用了temp.c中定义的static函数foo。如果直接编译,结果自然会出错。

  1. bash-2.05b$ gcc tt.c temp.c
  2. /tmp/ccO2uYBy.o(.text+0x11): In function `main':
  3. : undefined reference to `foo'
  4. collect2: ld returned 1 exit status
复制代码

但是仔细观察一下,就会发现提示的信息是"ld returned 1 exit status",也就是说是链接时报错。
如果我们分步编译,这个过程就会看得更清楚:

  1. bash-2.05b$ gcc -c tt.c -o tt.o
  2. bash-2.05b$ gcc -c temp.c -o temp.o
  3. bash-2.05b$ gcc tt.o temp.o
  4. tt.o(.text+0x11): In function `main':
  5. : undefined reference to `foo'
  6. collect2: ld returned 1 exit status
复制代码

可以看到,单独把tt.c编译成tt.o和把temp.c编译成temp.o时,编译器都 没有报错。而在将temp.o和tt.o链接在一起时,错误才发生。如果真的象你理解的那样“编译器根据static决定符号可不可以被外部引用”那就应该在编译tt.c时就应该报错。但事实上,这是不可能的。由于编译器是以文件为单位来一个文件一个文件的编译的,gcc在编译tt.c时,它是不知道还有一个temp.c的文件的,它更不会知道foo在temp.c中被定义成static的。tt.c中的foo函数,对于编译器来说,只是一个未定义的符号。它对这个符号的处理,也只能是把它标记成undefined,然后导出到obj文件中,让链接器来处理。
用nm tt.o可以看到这个导出的符号:

  1. bash-2.05b$ nm tt.o
  2.          U foo
  3. 00000000 T main
复制代码

正因为编译器有这种“只见树木不风森林”的特点,全局范围的符号解析只能由链接器完成。当链接器在所有目标文件中都找不到可以全局解析的foo符号时(temp.o的foo是LOCAL的,不能在全局范围内解析),此时才会报错。
回复 支持 反对

使用道具 举报

发表于 2005-4-25 11:17:11 | 显示全部楼层
呵呵,讨论了那么长时间,原来已经开始讨论技术性问题啦
我也说两句吧,感觉其实像 static, extern 给谁用应该是编译器相关的,语言只保证用户这样用,会达到怎样的效果,而并没有要求要达到这样的效果,应该怎么做.这个怎么做,应该是由各自编译器自己决定吧

ps:这里说的编译器,包括连接,准确的说应该是编译系统或者编译程序吧
回复 支持 反对

使用道具 举报

发表于 2005-4-25 14:55:28 | 显示全部楼层
Post by kj501
可以看到,单独把tt.c编译成tt.o和把temp.c编译成temp.o时,编译器都 没有报错。而在将temp.o和tt.o链接在一起时,错误才发生。如果真的象你理解的那样“编译器根据static决定符号可不可以被外部引用”那就应该在编译tt.c时就应该报错。但事实上,这是不可能的。由于编译器是以文件为单位来一个文件一个文件的编译的,gcc在编译tt.c时,它是不知道还有一个temp.c的文件的,它更不会知道foo在temp.c中被定义成static的。tt.c中的foo函数,对于编译器来说,只是一个未定义的符号。它对这个符号的处理,也只能是把它标记成undefined,然后导出到obj文件中,让链接器来处理。
正因为编译器有这种“只见树木不风森林”的特点,全局范围的符号解析只能由链接器完成。当链接器在所有目标文件中都找不到可以全局解析的foo符号时(temp.o的foo是LOCAL的,不能在全局范围内解析),此时才会报错。


我们的理解都没有问题,只是表述得不一样而以。在tt.c中foo符号由于没有声明,根据C89它被默认为int foo()。C++就不让了,所有的外部符号都必须有声明,因为有函数重载的问题。C99好像也要求对外部函数有声明。
“编译器根据static决定符号可不可以被外部引用”这句话没有错,tt.c全文都没有出现static这个单词,正如你说的,编译器看到的只是单个文件,那tt.c的编译与static关键字的作用之间应该没有任何关系吧?
回复 支持 反对

使用道具 举报

发表于 2005-4-25 16:48:44 | 显示全部楼层
Post by Tetris
我们的理解都没有问题,只是表述得不一样而以。在tt.c中foo符号由于没有声明,根据C89它被默认为int foo()。C++就不让了,所有的外部符号都必须有声明,因为有函数重载的问题。C99好像也要求对外部函数有声明。
“编译器根据static决定符号可不可以被外部引用”这句话没有错,tt.c全文都没有出现static这个单词,正如你说的,编译器看到的只是单个文件,那tt.c的编译与static关键字的作用之间应该没有任何关系吧?

我所理解的编译器是狭义的编译器,也就是不包括链接器的。如果你理解的编译器是指包含链接器在内的广义的编译工具,那么“编译器根据static决定符号可不可以被外部引用”这句话也就没有问题。
现代的编译器都能在后台自动调用链接器,所有很多时候两者常常被混为一谈。
回复 支持 反对

使用道具 举报

发表于 2005-4-25 22:57:41 | 显示全部楼层
Post by kj501
我所理解的编译器是狭义的编译器,也就是不包括链接器的。如果你理解的编译器是指包含链接器在内的广义的编译工具,那么“编译器根据static决定符号可不可以被外部引用”这句话也就没有问题。
现代的编译器都能在后台自动调用链接器,所有很多时候两者常常被混为一谈。



不是吧?你理解我的话好像有一些问题吧。我指的编译器就是狭义的啊。
编译器(狭义)根据全局变量或函数前是否有static关键字,决定elf格式目标文件中符号的bind类型,没错吧?也就是决定符号(链接时)是不是可以被其它模块引用。或者可以解理为,被定义为static的符号只能内部使用,外部是看不到的。
看来自然语言就是没有程序那么好理解:)
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 注册

本版积分规则

快速回复 返回顶部 返回列表