LinuxSir.cn,穿越时空的Linuxsir!

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

C必须包含main函数吗?

[复制链接]
发表于 2004-10-7 18:00:24 | 显示全部楼层
段错误是程序造成的,不是编译的问题。
我把程序改成下面这样,段错误就消失了:

  1. #include <stdio.h>

  2. int x(int argc, char *argv[])
  3. {
  4.     printf("Hello, world!\n");
  5.     exit(0);
  6. }
复制代码

我想作为入口使用的函数,是没有其它函数调用它的,这时用return 0返回一个值,肯定会出错。呵呵。。。
发表于 2004-10-7 18:07:32 | 显示全部楼层
函数的返回值一般是放在 eax 中的,上面的两个汇编代码在这一点上没有区别呀。我也不知道执行 exit(0) 的话汇编指令是什么,但肯定是初始化的问题。下面的程序也有段错误:
  1. int x(int argc, char *argv[])
  2. {
  3.         printf("%s\n", argv[0]);
  4.         exit(0);
  5. }
复制代码

我用上面的方法也没有成功。
btw: 用了 exit(0) 之后,第一个程序确实没有段错误了。
发表于 2004-10-7 18:10:19 | 显示全部楼层
我要去吃饭了。明天继续。
发表于 2004-10-8 11:10:12 | 显示全部楼层
比较一下使用main作为入口函数和自定义入口函数生成的可执行文件就知道了。默认情况下,链接器还放了初始化代码在里面的。
发表于 2004-10-8 12:38:00 | 显示全部楼层
下面是我的观点。

gcc 中调用一个普通函数时,首先将函数的参数压入堆栈 (按从右至左的顺序),然后压入程序记数器 (pc) 的值,就将新的 pc 值装入执行函数中的代码了。从上面的汇编代码来看,手工的与自动的代码在这一点上没有太大的区别。

如果是 main 函数,编译器会在编译时做一些其他的工作,将初始化的代码插入到 main() 之前或之中。如果只是一般的函数,编译器是不会做这些工作的。至于连接器,只是做将一个或几个二进制文件组合起来,重新定位它们的数据并将符号集中到一起这样简单的工作,是不会插入新的代码的。

下面是自动编译的代码产生的指令:
  1. (gdb) b 1
  2. Breakpoint 1 at 0x80482c0: file ../sysdeps/i386/elf/start.S, line 1.
  3. (gdb) run
  4. Starting program: /home/herbert/x2
  5. Breakpoint 1, _start () at ../sysdeps/i386/elf/start.S:47
  6. 47      ../sysdeps/i386/elf/start.S: 没有那个文件或目录.
  7.         in ../sysdeps/i386/elf/start.S
  8. Current language:  auto; currently asm
  9. (gdb) disassemble
  10. Dump of assembler code for function _start:
  11. 0x080482c0 <_start+0>:  xor    %ebp,%ebp
  12. 0x080482c2 <_start+2>:  pop    %esi
  13. 0x080482c3 <_start+3>:  mov    %esp,%ecx
  14. 0x080482c5 <_start+5>:  and    $0xfffffff0,%esp
  15. 0x080482c8 <_start+8>:  push   %eax
  16. 0x080482c9 <_start+9>:  push   %esp
  17. 0x080482ca <_start+10>: push   %edx
  18. 0x080482cb <_start+11>: push   $0x8048410
  19. 0x080482d0 <_start+16>: push   $0x80483b0
  20. 0x080482d5 <_start+21>: push   %ecx
  21. 0x080482d6 <_start+22>: push   %esi
  22. 0x080482d7 <_start+23>: push   $0x8048384
  23. 0x080482dc <_start+28>: call   0x80482a0 <_init+40>
  24. 0x080482e1 <_start+33>: hlt
  25. 0x080482e2 <_start+34>: nop
  26. 0x080482e3 <_start+35>: nop
  27. End of assembler dump.
复制代码


下面是手工指定的指令:
  1. (gdb) b 1
  2. No symbol table is loaded.  Use the "file" command.
  3. (gdb) run
  4. Starting program: /home/herbert/x
  5. (no debugging symbols found)...(no debugging symbols found)...
  6. Program received signal SIGSEGV, Segmentation fault.
  7. 0x080481e9 in x ()
  8. (gdb) disassemble
  9. Dump of assembler code for function x:
  10. 0x080481e0 <x+0>:       push   %ebp
  11. 0x080481e1 <x+1>:       mov    %esp,%ebp
  12. 0x080481e3 <x+3>:       sub    $0x8,%esp
  13. 0x080481e6 <x+6>:       mov    0xc(%ebp),%eax
  14. 0x080481e9 <x+9>:       mov    (%eax),%eax
  15. 0x080481eb <x+11>:      mov    %eax,0x4(%esp)
  16. 0x080481ef <x+15>:      movl   $0x8048207,(%esp)
  17. 0x080481f6 <x+22>:      call   0x80481c0
  18. 0x080481fb <x+27>:      movl   $0x0,(%esp)
  19. 0x08048202 <x+34>:      call   0x80481d0
  20. End of assembler dump.
复制代码


我认为段错误就是没有调用 <_init+40> 造成的。(<_init+40> 是什么呢?) 没有调用它,就没有初始化页表 (或段寄存器,我不知道在 Linux 中是哪个),这样系统就没有为这个进程分配实际的物理内存,应该就是产生段错误的原因吧。
发表于 2004-10-8 12:53:29 | 显示全部楼层
还有一点,我本来想检查一下本贴中第 1 个程序的机器指令的,可是结果像下面这个样子,看来我还是不会使用 gdb 啊。
  1. (gdb) b 1
  2. No symbol table is loaded.  Use the "file" command.
  3. (gdb) run
  4. Starting program: /home/herbert/z
  5. (no debugging symbols found)...(no debugging symbols found)...Hello, world!
  6. Program exited normally.
  7. (gdb)
复制代码
发表于 2004-10-8 13:55:06 | 显示全部楼层
最初由 herberteuler 发表
下面是我的观点。


我认为段错误就是没有调用 <_init+40> 造成的。(<_init+40> 是什么呢?) 没有调用它,就没有初始化页表 (或段寄存器,我不知道在 Linux 中是哪个),这样系统就没有为这个进程分配实际的物理内存,应该就是产生段错误的原因吧。


_init+40是__libc_start_main
发表于 2004-10-8 17:07:02 | 显示全部楼层
最初由 kj501 发表
段错误是程序造成的,不是编译的问题。
我把程序改成下面这样,段错误就消失了:

  1. #include <stdio.h>

  2. int x(int argc, char *argv[])
  3. {
  4.     printf("Hello, world!\n");
  5.     exit(0);
  6. }
复制代码

我想作为入口使用的函数,是没有其它函数调用它的,这时用return 0返回一个值,肯定会出错。呵呵。。。

今天又想了一下,觉得不对。我写了一个最简单的程序:

  1. int main()
  2. {
  3. }
复制代码

然后运行,也出现段错误。这个程序的汇编代码如下:

  1.         .file        "tt.c"
  2.         .text
  3. .globl main
  4.         .type        main, @function
  5. main:
  6.         pushl        %ebp
  7.         movl        %esp, %ebp
  8.         subl        $8, %esp
  9.         andl        $-16, %esp
  10.         movl        $0, %eax
  11.         subl        %eax, %esp
  12.         leave
  13.         ret
  14.         .size        main, .-main
  15.         .section        .note.GNU-stack,"",@progbits
  16.         .ident        "GCC: (GNU) 3.3.2 (Mandrake Linux 10.0 3.3.2-6mdk)"
复制代码

从程序中可以看出,只要没有调用exit(),都会出现段错误。我想原因应该是这样:每个程序被加载到内存中运行之后,程序计数器pc的指针就指向下一条要运行的指令。如果不在程序结束时调用exit()通知操作系统结束进程的运行。那么操作系统不知道程序的运行已经结束,于是pc继续把进程最后一条有效指令之后的数据(因为这一部分已经不是真正的程序指令)当成是指令进行取指,这个越界访问的错误被操作系统发现,于是就Segmentation fault了。
发表于 2004-10-8 17:14:18 | 显示全部楼层
这个最简单的程序是用 gcc 编译的吗?在我这里用 gcc 编译执行没有段错误啊。

另外,从上面用 gdb 调试的情况来看,后面一个程序是在开始执行之前就收到了 SIGSEGV 信号的,也许连物理内存还没有给这个进程分配。我在内核研究版问了创建新进程的代码在哪里的问题但很久都没有人回答,如果我们能看到这部分的代码就清楚了。
发表于 2004-10-8 17:17:10 | 显示全部楼层
下面是调试的过程:
  1. (gdb) b 1
  2. Breakpoint 1 at 0x8048290: file ../sysdeps/i386/elf/start.S, line 1.
  3. (gdb) b main
  4. Breakpoint 2 at 0x804835a
  5. (gdb) run
  6. Starting program: /home/herbert/y
  7. Breakpoint 1, _start () at ../sysdeps/i386/elf/start.S:47
  8. 47      ../sysdeps/i386/elf/start.S: 没有那个文件或目录.
  9.         in ../sysdeps/i386/elf/start.S
  10. Current language:  auto; currently asm
  11. (gdb) disassemble
  12. Dump of assembler code for function _start:
  13. 0x08048290 <_start+0>:  xor    %ebp,%ebp
  14. 0x08048292 <_start+2>:  pop    %esi
  15. 0x08048293 <_start+3>:  mov    %esp,%ecx
  16. 0x08048295 <_start+5>:  and    $0xfffffff0,%esp
  17. 0x08048298 <_start+8>:  push   %eax
  18. 0x08048299 <_start+9>:  push   %esp
  19. 0x0804829a <_start+10>: push   %edx
  20. 0x0804829b <_start+11>: push   $0x80483d0
  21. 0x080482a0 <_start+16>: push   $0x8048370
  22. 0x080482a5 <_start+21>: push   %ecx
  23. 0x080482a6 <_start+22>: push   %esi
  24. 0x080482a7 <_start+23>: push   $0x8048354
  25. 0x080482ac <_start+28>: call   0x804827c <_init+40>
  26. 0x080482b1 <_start+33>: hlt
  27. 0x080482b2 <_start+34>: nop
  28. 0x080482b3 <_start+35>: nop
  29. End of assembler dump.
  30. (gdb) continue
  31. Continuing.
  32. Breakpoint 2, 0x0804835a in main ()
  33. (gdb) disassemble
  34. Dump of assembler code for function main:
  35. 0x08048354 <main+0>:    push   %ebp
  36. 0x08048355 <main+1>:    mov    %esp,%ebp
  37. 0x08048357 <main+3>:    sub    $0x8,%esp
  38. 0x0804835a <main+6>:    and    $0xfffffff0,%esp
  39. 0x0804835d <main+9>:    mov    $0x0,%eax
  40. 0x08048362 <main+14>:   sub    %eax,%esp
  41. 0x08048364 <main+16>:   leave
  42. 0x08048365 <main+17>:   ret
  43. 0x08048366 <main+18>:   nop
  44. 0x08048367 <main+19>:   nop
  45. 0x08048368 <main+20>:   nop
  46. 0x08048369 <main+21>:   nop
  47. 0x0804836a <main+22>:   nop
  48. 0x0804836b <main+23>:   nop
  49. 0x0804836c <main+24>:   nop
  50. 0x0804836d <main+25>:   nop
  51. 0x0804836e <main+26>:   nop
  52. 0x0804836f <main+27>:   nop
  53. End of assembler dump.
  54. (gdb) next
  55. Single stepping until exit from function main,
  56. which has no line number information.
  57. 0x4003ddc6 in __libc_start_main () from /lib/libc.so.6
  58. (gdb) next
  59. Single stepping until exit from function __libc_start_main,
  60. which has no line number information.
  61. Program exited normally.
  62. (gdb)
复制代码
您需要登录后才可以回帖 登录 | 注册

本版积分规则

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