(一)main函数的argc、argv实现本质

对于main函数的argc和argv作用:

[root@localhost valgrind_test]# ./test  a b

则argc=2,argv[0]="./test ",argv[1]="a",argv[2]="b"

现在讨论这个实现原理:分为几步骤分析:


1、首先编写一个什么事情都不做的汇编文件,看看传入的命令行参数对于汇编而言是如何处理的:

[root@localhost]# cat test.s

.text
.global _start
_start:
        nop
        nop
        nop
        movl    $0,     %ebx    # 传给_exit的参数
        movl    $1,     %eax    # 系统调用号,_exit
        int     $0x80



编译文件:

[root@localhost ]# as -g -o test.o test.s 
[root@localhost]# ld -o test test.o 

调试文件:

root@localhost]# gdb test

(gdb) b 7   //设置断点,在nop语句
Breakpoint 1 at 0x8048056: file test.s, line 7.
(gdb) r 2 2 2  //命令行参数,相当于./test 2 2 2
Starting program: /home/long/valgrind_test/test 2 2 2  //注意,这里是绝对路径!不是相对路径(不是./test)
Breakpoint 1, _start () at test.s:7
7               nop
Current language:  auto; currently asm
(gdb) i r      //打印寄存器的值
eax            0x0      0
ecx            0x0      0
edx            0x0      0
ebx            0x0      0
esp            0xbfd1dfe0       0xbfd1dfe0
ebp            0x0      0x0
esi            0x0      0
edi            0x0      0
eip            0x8048056        0x8048056 <_start+2>
eflags         0x212    [ AF IF ]
(gdb) x/6xw 0xbfd1dfe0  //读取堆栈寄存器6个字节(前5个字节有效)
0xbfd1dfe0:     0x00000004      0xbfd1fbd2      0xbfd1fbf0      0xbfd1fbf2
0xbfd1dff0:     0xbfd1fbf4      0x00000000
(gdb) x/40c 0xbfd1fbd2    //堆栈第一个字节存放命令行参数的个数(包括文件名字),这里是4。接下来的字节就是这些参数的存放的地址,包括:"/home/long/valgrind_test/test"、"2"、"2"存放的地址:
0xbfd1fbd2:     47 '/'  104 'h' 111 'o' 109 'm' 101 'e' 47 '/'  108 'l' 111 'o'
0xbfd1fbda:     110 'n' 103 'g' 47 '/'  118 'v' 97 'a'  108 'l' 103 'g' 114 'r'
0xbfd1fbe2:     105 'i' 110 'n' 100 'd' 95 '_'  116 't' 101 'e' 115 's' 116 't'
0xbfd1fbea:     47 '/'  116 't' 101 'e' 115 's' 116 't' 0 '\0'  50 '2'  0 '\0'
0xbfd1fbf2:     50 '2'  0 '\0'  50 '2'  0 '\0'  72 'H'  79 'O'  83 'S'  84 'T'



得出结论:传入的命令行参数,会把参数个数、文件名字所在地址、参数所在地址压入堆栈esp。


2、实现汇编调用C语言函数,探讨汇编和C函数参数如何传递:

代码:

[root@localhost]# cat test.s

.text
.global _start
.global print_argv
_start:
        nop
        movl    $2,     %eax
        pushl   %eax   //把2压入栈
        call    main
        movl    $0,     %ebx    # 传给_exit的参数
        movl    $1,     %eax    # 系统调用号,_exit
        int	  $0x80


[root@localhost]# cat test.c

#include<stdio.h>
#include<stdlib.h>


int main(int argc,int argv)
{
        printf("0x%x,0x%x\n",argc,argv);
        return 0;
}


编译运行:

[root@localhost]# gcc  -nostartfiles -o test test.c test.s

[root@localhost]# ./test  2  2  2
0x2,0x4
得出结论:调用C函数,实际传入的实参就是esp堆栈出栈得到的。




3、实现main的argc和argv:

[root@localhost]# cat test.s

.text
.global _start
.global print_argv
.type _start,@function
_start:
        movl    %esp,   %eax  //得到栈顶地址
        addl    $4,     %eax  //栈顶地址+4,也就是栈的第二个元素地址,也就是上图0xbfd1fbd2存放地址(&argv[0])
        pushl   %eax          //压栈


        movl    4(%esp),%eax  //栈顶地址+4的内容压栈,也就是argc的值
        pushl   %eax
        call    main


        movl    $0,     %ebx    # 传给_exit的参数
        movl    $1,     %eax    # 系统调用号,_exit
        int	  $0x80


[root@localhost valgrind_test]# cat test.c

#include<stdio.h>
#include<stdlib.h>


int main(int argc,char *argv[])
{
        int i=0;
        for(i=0;i<argc;i++)
                printf("[%d]:%s\n",i,argv[i]);
}


编译运行:

[root@localhost]# gcc  -nostartfiles -o test test.c test.s

[root@localhost valgrind_test]# ./test 2 2 2
[0]:./test
[1]:2
[2]:2
[3]:2

示意图:







最后一个问题,实际gcc编译一个C文件的时候,都是把包含main的C文件编译为.o文件,然后和其他文件的.o进行链接,其中就包括类似前面汇编功能的文件,所以实际argc和argv的功能实现是在编译器帮我们完成了。


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: b'c\xe8\xaf\xad\xe8\xa8\x80main\xe5\x87\xbd\xe6\x95\xb0argc\xe5\x92\x8cargv' 是一个字节串,需要将其解码为字符串。 解码后表达的意思是:这是一个 C 语言的主函数,其中 argcargv 是参数。 ### 回答2: 在C语言中,main函数是程序的入口点。它的形式参数argcargv用于接收程序启动时传递给它的参数。 argc是一个整型变量,它表示传递给程序的参数个数。通常情况下,如果在命令行中运行程序并且没有提供参数,argc的值为1(表示程序名称本身就是一个参数)。如果提供了一个参数,argc的值为2,以此类推。 argv是一个字符指针数组,它包含传递给程序的参数列表。argv[0]是程序名称本身,而argv[1]、argv[2]等则是传递给程序的其他参数。argv的长度等于argc。需要注意的是,所有的参数都以字符串形式传递给程序,因此需要使用适当的函数将它们转换为其他类型。 下面是一个简单的例子,它演示了如何使用argcargv: #include <stdio.h> int main(int argc, char *argv[]) { int i; printf("argc = %d\n", argc); for (i = 0; i < argc; i++) { printf("argv[%d] = %s\n", i, argv[i]); } return 0; } 在这个例子中,程序接收到的参数个数和每个参数的值都会被打印出来。如果没有传递参数,那么输出结果应该类似于: $ ./program argc = 1 argv[0] = ./program 如果传递了一些参数,那么输出结果应该类似于: $ ./program arg1 arg2 arg3 argc = 4 argv[0] = ./program argv[1] = arg1 argv[2] = arg2 argv[3] = arg3 因此,通过main函数中的argcargv参数,我们可以接收并处理用户传递给程序的参数,这对于开发命令行工具和参数化测试很有用。 ### 回答3: C语言中的main函数有两个参数,分别是argcargv。这两个参数对于理解和编写C语言程序都非常重要。 首先,argc是一个整数类型的变量,表示程序运行时传给main函数的命令行参数的数量。其中,argv是一个字符串类型的数组,用来存储命令行参数的值。在程序中,可以通过引用argv数组中的元素来使用命令行参数。 例如,命令行输入"./program arg1 arg2",则argc的值为3,argv数组中的第一个元素是程序名称"./program",第二个元素是"arg1",第三个元素是"arg2"。程序中可以通过下标访问argv数组元素来获取命令行参数的值,例如argv[1]表示第一个命令行参数arg1的值。 其次,使用argcargv参数还可以实现命令行程序的交互操作。在程序中,可以根据用户的输入来执行不同的操作或返回不同的结果。可以通过解析argv数组元素的值来确定程序的执行方式。 例如,一个计算器程序可以通过命令行参数接收用户输入的数字和操作符,然后计算结果并输出到屏幕上。另外一个程序可以根据命令行参数打开指定的文件并进行操作等等。 总之,理解和使用main函数argcargv参数对于编写高效、灵活的C语言程序非常重要。可以通过这两个参数实现程序的交互操作和应对不同的处理需求。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值