title: printf不定参数

tags: C ARM

date: 2018-10-21 12:14:58

不定参数的传递

函数调用时参数传递是使用堆栈来实现的,参数入栈顺序是从右向左,在被调用函数 (Callee) 返回后,由调用方 (Caller)调整堆栈,由于这种约定,C调用约定允许函数的参数的个数是不固定的,这也是C语言的一大特色。因为每个调用的地方都需要生成一段清理堆栈的代码,所以最后生成的目标文件较__stdcall、__fastcall调用方式要大,因为每一个主调函数在每个调用的地方都需要生成一段清理堆栈的代码。C调用约定在返回前,要作一次堆栈平衡,也就是参数入栈了多少字节,就要弹出来多少字节.这样很安全.

C程序栈底为高地址,栈顶为低地址,ARM栈一般为向下生长,C51为向上生长,但是在参数传递的时候,没有使用这个栈,而是使用固定地址,他形成的也是一个向下生长的栈,具体看51中的参数传递

一些规定

  • 不定参数的列表必须在整个函数的参数列表的最后
  • 函数调用时参数传递是使用堆栈来实现的
  • 在51 和arm中,不论怎么传递参数,都确保是向下生长的栈(51是模拟的一个),第一个参数在最低地址即可,否则如果是向上生长的堆栈,取参数的宏就需要做改变了
  • 注意传递取地址的时候,注意字节对其
  • printf 中 float 类型的数据默认按照double类型传递

使用不定参数

  1. 获取定参地址,首地址
  2. 对连续地址进行依次取值,移动指针到下一个参数的地址

确定不定参数个数

  • 在类型固定的参数中指明后面有多少个参数以及他们的类型。printf就是采用的这种方法,它的format参数指明后面每个参数的类型。
  • 指定一个结束参数。这种情况一般是不定参数拥有同样的类型,我们可以指定一个特定的值来表示参数列表结束。

使用宏来操作不定参地址

简单的代码实例:

void TestPush(const char * format,...)
{ //format 本身是个指针,指向 const char
//format 本身存在sp中,获取format的地址也就是堆栈所在的地址
//注意这里获取的format的地址,不能直接是format char * sp_start=(char* )&format;
void * pt; printf("arg[0]=%s\r\n",format); pt=(int*)(sp_start+sizeof(const char *));
printf("arg[1]=%d\r\n",*(int*)pt); pt=(double*)(pt+sizeof(int));
printf("arg[2]=%f\r\n",*(double*)pt); pt=(int*)(pt+sizeof(double));
printf("arg[3]=%d\r\n",*(int*)pt); //注意这里指针转换
//错误:(struct person_stu*)pt->name 先结合后面的pt->name,然后转换地址
//这里的->优先级高于低于地址转换 pt=(struct person_stu*)(pt+sizeof(int));
printf("arg[4]=struct person\r\n");
printf(" name=%s,age=%d,weight=%d\r\n",
((struct person_stu*)pt)->name,
((struct person_stu*)pt)->age,
((struct person_stu*)pt)->weight); }

注意:

tips 之前一直在想如果是结构体一个成员传递是不是有对齐的问题。。。后来想起来这东西是值传递啊,但是在传递比如 char 这个类型的时候,为了保证4字节对齐,应将其改为int va_arg(int) 而不是va_arg(char)

typedef char *  va_list;
#define _INTSIZEOF(n)   ( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) )// int 对齐
#define va_start(ap,v) ( ap = (va_list)&v + _INTSIZEOF(v) ) //查询第一个参数的值,同时指针指向下一个
#define va_arg(ap,t)    (*(t *)(ap = ap + _INTSIZEOF(t), ap - _INTSIZEOF(t))) //取值,取值
逗号表达式返回后一个表达式的值
表达式1:ap = ap + _INTSIZEOF(t)
表达式2:*(t *)(ap + _INTSIZEOF(t)) //因为第一个表达式加了地址偏移了
#define va_end(ap)      ( ap = (va_list)0 )

注意:

由于参数的地址用于va_start宏,所以参数不能声明为寄存器变量或数组类型,数组的话是地址进去了应该。

51的宏有所不同,具体去看STDARG.H

#define __STDARG_H__

#ifndef NULL
#define NULL ((void *) 0)
#endif #ifndef _VA_LIST_DEFINED
typedef char *va_list;
#define _VA_LIST_DEFINED
#endif #define va_start(ap,v) ap = (va_list)&v + sizeof(v)
#define va_arg(ap,t) (((t *)ap)++[0])
#define va_end(ap) #endif

ARM传递参数

  • 当少于四个时,按从左到右的顺序依次放在r0,r1,r2,r3中;
  • 当多于四个时,前四个放在r0,r1,r2,r3中,剩余的放在堆栈中,最后一个参数先入栈,第五个参数最后入栈,即从右到左入栈

例子如下:,传递参数先在r0,r1,r2,r3,然后放进栈中

    48:            testStack("1",(int)2,(int)3,(int)4,(int)5,(int)6,(int)7,(int)8,(int)9,(int)10,(int)11,(int)12,(int)13);
0x0800028A 200D MOVS r0,#0x0D
0x0800028C 210C MOVS r1,#0x0C
0x0800028E 220B MOVS r2,#0x0B
0x08000290 230A MOVS r3,#0x0A
0x08000292 E9CD3205 STRD r3,r2,[sp,#0x14]
0x08000296 E9CD1007 STRD r1,r0,[sp,#0x1C]
0x0800029A 2009 MOVS r0,#0x09
0x0800029C 2108 MOVS r1,#0x08
0x0800029E 2207 MOVS r2,#0x07
0x080002A0 2306 MOVS r3,#0x06
0x080002A2 E9CD3201 STRD r3,r2,[sp,#0x04]
0x080002A6 E9CD1003 STRD r1,r0,[sp,#0x0C]
0x080002AA 2005 MOVS r0,#0x05
0x080002AC 2304 MOVS r3,#0x04
0x080002AE 2203 MOVS r2,#0x03
0x080002B0 2102 MOVS r1,#0x02
0x080002B2 9000 STR r0,[sp,#0x00]
0x080002B4 A014 ADR r0,{pc}+4 ; @0x08000308
0x080002B6 F7FFFFBD BL.W testStack (0x08000234)

使用时直接用堆栈,取出r0~~r3的值

    10: void testStack(char * format, ...)
0x08000232 4770 BX lr
11: {
0x08000234 B40F PUSH {r0-r3}
0x08000236 B570 PUSH {r4-r6,lr}
12: char *p = (char * )&format;
13: int i;
14: int b;
15:
16:
0x08000238 AC04 ADD r4,sp,#0x10 .........................

当参数个数较少时, 可能也就单纯的赋值到r0~~r3

#include "stdio.h"

int  a1(int b)
{
return (b)+1;
} long a[10];
void SystemInit(){;}
void testStack(int format, ...)
{
char *p = (char * )&format;
int i;
int b;
a[0]=format;
a[1]=(long)&format;
p = p + sizeof(int); b=(int)p;
a1(b); i = *((int *)p);
a[2]=*p;
a[3]=(long)p; a[4]=(long)p;
} int main()
{
while(1)
{
testStack((int)1);
testStack((int)1,(int)2);
testStack((int)1,(int)2,(int)3);
testStack((int)1,(int)2,(int)3,(int)4);
testStack((int)1,0x11e23344,(int)0x11223f43,(int)0x11d23342,(int)0xa1223341);
testStack((int)1,(int)2,(int)3,(int)4,(int)5,(int)6,(int)7,(int)8,(int)9,(int)10,(int)11,(int)12,(int)13);
}
}

实验结果,可以看出是sp里面的取值地址

a0=0x00000001
a1=0x200003F0
a2=0x00000002 其中有=44,因为第二个参数有不一样的
a3=0x200003F4

51中参数传递

testStack("555555555",(int)2,(int)3,(int)4,(int)5,(int)6,(int)7,(int)8,(int)9,(int)10);

C:0x0F66    7BFF     MOV      R3,#0xFF
C:0x0F68 7A16 MOV R2,#0x16
C:0x0F6A 7996 MOV R1,#0x96
C:0x0F6C 752800 MOV 0x28,#0x00
C:0x0F6F 752902 MOV 0x29,#0x02
C:0x0F72 752A00 MOV 0x2A,#0x00
C:0x0F75 752B03 MOV 0x2B,#0x03
C:0x0F78 752C00 MOV 0x2C,#0x00
C:0x0F7B 752D04 MOV 0x2D,#0x04
C:0x0F7E 752E00 MOV 0x2E,#0x00
C:0x0F81 752F05 MOV 0x2F,#0x05
C:0x0F84 753000 MOV 0x30,#0x00
C:0x0F87 753106 MOV 0x31,#0x06
C:0x0F8A 753200 MOV 0x32,#0x00
C:0x0F8D 753307 MOV 0x33,#0x07
C:0x0F90 753400 MOV 0x34,#0x00
C:0x0F93 753508 MOV 0x35,#0x08
C:0x0F96 753600 MOV 0x36,#0x00
C:0x0F99 753709 MOV 0x37,#0x09
C:0x0F9C 753800 MOV 0x38,#0x00
C:0x0F9F 75390A MOV 0x39,#0x0A
C:0x0FA2 121515 LCALL testStack(C:1515) void testStack(char * format, ...)
{
char *p = (char * )&format;
int i;
int b;
printf("arg1 : %s\n",format);
printf("&arg1 : %p\n",&format); /*指针对连续空间操作时: 1) 取值 2)移动指针*/
p = p + sizeof(char *);
b=(int)p;
a1(b);
i = *((int *)p);
printf("arg2 : %d\n",i);
printf("&arg2 : %p\n",p);
} 打印
arg1 : 1
&arg1 : i:0025
arg2 : 2
&arg2 : i:0028

函数调用的情况如下,可以看出先 MOV R1,R2,R3 -->0x27,0x26,0x25,之前R1,R2,R3正好是第一个参数的值,而之前其使用固定地址传值的地址正好是0x28-->0x39,其实后面再使用这个函数的时候,0x28总保持不变的,0x39根据可变参数个数来变,这就好办了,类似于知道栈顶地址为0x28,栈底我们可以根据不同方法来确认了(向下生长的栈)。

     9: void testStack(char * format, ...)
C:0x0418 8B25 MOV 0x25,R3
C:0x041A 8A26 MOV 0x26,R2
C:0x041C 8927 MOV 0x27,R1
10: {
11: char *p = (char * )&format;
12: int i;
13: int b;
14:
15:
C:0x041E 753700 MOV 0x37,#0x00
C:0x0421 753800 MOV 0x38,#0x00
C:0x0424 753925 MOV 0x39,#0x25
16: printf("arg1 : %s\n",format);
C:0x0427 8B3D MOV 0x3D,R3
C:0x0429 8A3E MOV 0x3E,R2
C:0x042B 893F MOV 0x3F,R1
C:0x042D 7BFF MOV R3,#0xFF
C:0x042F 7A04 MOV R2,#0x04
C:0x0431 79E3 MOV R1,#0xE3
C:0x0433 120065 LCALL PRINTF(C:0065) 17: printf("&arg1 : %p\n",&format);
18:
19:
20: /*指针对连续空间操作时: 1) 取值 2)移动指针*/
C:0x0436 753D00 MOV 0x3D,#0x00
C:0x0439 753E00 MOV 0x3E,#0x00
C:0x043C 753F25 MOV 0x3F,#0x25
C:0x043F 7BFF MOV R3,#0xFF
C:0x0441 7A04 MOV R2,#0x04
C:0x0443 79EE MOV R1,#0xEE
C:0x0445 120065 LCALL PRINTF(C:0065)
21: p = p + sizeof(char *);
22:
C:0x0448 7403 MOV A,#0x03
C:0x044A 2539 ADD A,0x39
C:0x044C F539 MOV 0x39,A
C:0x044E E4 CLR A
C:0x044F 3538 ADDC A,0x38
C:0x0451 F538 MOV 0x38,A
23: b=(int)p;
C:0x0453 FE MOV R6,A
C:0x0454 AF39 MOV R7,0x39
24: a1(b); 25:
C:0x0456 12056F LCALL a1(C:056F)
26: i = *((int *)p);
C:0x0459 AB37 MOV R3,0x37
C:0x045B AA38 MOV R2,0x38
C:0x045D A939 MOV R1,0x39
C:0x045F 12035F LCALL C?ILDPTR(C:035F)
27: printf("arg2 : %d\n",i);
C:0x0462 7BFF MOV R3,#0xFF
C:0x0464 7A04 MOV R2,#0x04
C:0x0466 79FA MOV R1,#0xFA
C:0x0468 85F03D MOV 0x3D,B(0xF0)
C:0x046B F53E MOV 0x3E,A
C:0x046D 120065 LCALL PRINTF(C:0065)
28: printf("&arg2 : %p\n",p);
29:
30:
31: }

打印的结果如下,符合预期.R1,R2,R3 存储第一个参数的地址,后面的参数使用固定地址,最左边的参数所在地址越低,这就导致了和 使用向下生长的栈,且参数从右向左是一致的效果,然后在函数调用的时候讲R1,R2,R3的值再赋值给更低的固定地址。

arg1 : 1
&arg1 : i:0025
arg2 : 2
&arg2 : i:0028

main.c 如下,使用软件仿真即可

#include "stdio.h"
#include <reg51.h> int a1(int b)
{
return (b)+1;
} void testStack(char * format, ...)
{
char *p = (char * )&format;
int i;
int b;
printf("arg1 : %s\n",format);
printf("&arg1 : %p\n",&format);
/*指针对连续空间操作时: 1) 取值 2)移动指针*/
p = p + sizeof(char *); b=(int)p;
a1(b); i = *((int *)p);
printf("arg2 : %d\n",i);
printf("&arg2 : %p\n",p);
}
void t()
{
SCON= 0x50;/*SCON:工作模式1,8-bit UART,允许接收*/
TMOD |= 0x20;/*TMOD:定时器T1,工作模式2, 8位自动重载方式*/
TH1= 0xf3;/*当波特率为2400时,定时器初值*/
TR1= 1;/*定时器T1开始运行*/
TI= 1;/*允许发送数据*/
}
#include<intrins.h> // 声明了void _nop_(void);
void main()
{
_nop_(); // 产生一条NOP指令
_nop_(); // 产生一条NOP指令
_nop_(); // 产生一条NOP指令
_nop_(); // 产生一条NOP指令
_nop_(); // 产生一条NOP指令
_nop_(); // 产生一条NOP指令
_nop_(); // 产生一条NOP指令
t();
{
char * str="kakakq=%d,%d,%d,%d,%d,%d\r\n";
printf(str,(int)1,(int)2,(int)3,(int)4,(int)5,(int)6);
}
while(1)
{
testStack("1",(int)2,(int)3,(int)4,(int)5);
_nop_(); // 产生一条NOP指令
}
}

printf 进制转换关键算法

unsigned char hex_tab[]={'0','1','2','3','4','5','6','7',\
'8','9','a','b','c','d','e','f'};
do{
*--s = hex_tab[m%base];
count++;
}while ((m /= base) != 0);
// n 数字
// base 进制
// lead 前导字符
// maxwith 字符宽度
static int out_num(long n, int base,char lead,int maxwidth)
{
unsigned long m=0;
char buf[MAX_NUMBER_BYTES], *s = buf + sizeof(buf);
int count=0,i=0; *--s = '\0'; if (n < 0){
m = -n;
}
else{
m = n;
} do{
*--s = hex_tab[m%base];
count++;
}while ((m /= base) != 0); if( maxwidth && count < maxwidth){
for (i=maxwidth - count; i; i--)
*--s = lead;
} if (n < 0)
*--s = '-'; return outs(s);
}

除法

刚开始的Makefile如下,会报错提示除法找不到

all:
arm-linux-gcc -c -o start.o start.S
arm-linux-gcc -c -o led.o led.c
arm-linux-gcc -c -o uart.o uart.c
arm-linux-gcc -c -o my_printf.o my_printf.c
arm-linux-gcc -c -o main.o main.c arm-linux-ld -Ttext 0 start.o led.o uart.o my_printf.o main.o -o uart.elf arm-linux-objcopy -O binary -S uart.elf uart.bin
arm-linux-objdump -D uart.elf > uart.dis
clean:
rm *.bin *.o *.elf *.dis

网上搜索说加入libgcc.a我们搜索下gcc的路径,然后搜索这个文件并加入,发现还是失败了

which gcc 

$ find /opt/gcc-3.4.5-glibc-2.3.6/ -name libgcc.a
$ /opt/gcc-3.4.5-glibc-2.3.6/lib/gcc/arm-linux/3.4.5/libgcc.a

l链接的时候加入,失败

arm-linux-ld -Ttext 0 start.o led.o uart.o  /opt/gcc-3.4.5-glibc-2.3.6/lib/gcc/arm-linux/3.4.5/libgcc.a  my_printf.o main.o -o uart.elf

视频上说使用,然后链接.这样成功了,但是不知道怎么弄的

arm-linux-gcc -c -o lib1funcs.o lib1funcs.S

空间大小

但是这样编译出来的代码有30多k

book@100ask:~/stu/004-printf/3th$ ls -l *.bin
-rwxrwxr-x 1 book book 36328 10月 21 16:23 uart.bin

查看下反汇编的section ,发现代码段在0起始,但是数据段在00008dd8,这个值差不多就是30多k了

tips: vim使用/ 启用搜索,确认回车后,使用 n向前,N向后搜索

 Disassembly of section .data:
935
936 00008dd8 <__data_start>:
937 8dd8: 33323130 teqcc r2, #12 ; 0xc
938 8ddc: 37363534 undefined
939 8de0: 62613938 rsbvs r3, r1, #917504 ; 0xe0000
940 8de4: 66656463 strvsbt r6, [r5], -r3, ror #8
941 Disassembly of section .comment:

重定位数据段的位置,查看下这个段上面的只读数据段的尾巴,视频的代码在e10)只需要稍后一点定义即可,加入选项-Tdata 0xe80

book@100ask:~/stu/004-printf/3th$ ls -l *.bin
-rwxrwxr-x 1 book book 3728 10月 21 16:31 uart.bin

最终Makefile如下

all:
arm-linux-gcc -c -o start.o start.S
arm-linux-gcc -c -o led.o led.c
arm-linux-gcc -c -o uart.o uart.c arm-linux-gcc -c -o lib1funcs.o lib1funcs.S
arm-linux-gcc -c -o my_printf.o my_printf.c
arm-linux-gcc -c -o main.o main.c arm-linux-ld -Ttext 0 -Tdata 0xe80 start.o led.o uart.o lib1funcs.o my_printf.o main.o -o uart.elf arm-linux-objcopy -O binary -S uart.elf uart.bin
arm-linux-objdump -D uart.elf > uart.dis rm *.o
clean:
rm *.bin *.o *.elf *.dis

printf不定参数的更多相关文章

  1. 不定参数函数原理以及实现一个属于自己的printf函数

    一.不定参数函数原理 二.实现一个属于自己的printf函数 参考博文:王爽汇编语言综合研究-函数如何接收不定数量的参数

  2. C技巧:结构体参数转成不定参数

    下面这段程序是一个C语言的小技巧,其展示了如何把一个参数为结构体的函数转成一个可变参数的函数,其中用到了宏和内建宏"__VA_ARGS__",下面这段程序可以在GCC下正常编译通过 ...

  3. C语言函数不定参数实现方式

    函数如何实现不定参数: 由于在C语言中没有函数重载,解决不定数目函数参数问题变得比较麻烦,即使采用C++,如果参数个数不能确定,也很难采用函数重载.对这种情况,提出了指针参数来解决问题. (1)va_ ...

  4. c++不定参数函数

    不定参数当年做为C/C++语言一个特长被很多人推崇,但是实际上这种技术并没有应用很多.除了格式化输出之外,我实在没看到多少应用.主要原因是这种技术比较麻烦,副作用也比较多,而一般情况下重载函数也足以替 ...

  5. C++ 中的不定参数与格式化字符串 # ## vsprintf

    日志打印或者格式字符串时,可能会用到不定参数的使用,这里记录一下. 格式化字符串有很多方法: snprintf std::stringstream # ##的使用 ##是一个连接符号,用于把参数连在一 ...

  6. C和C++中的不定参数

    在初学C的时候,我们都会用到printf函数来写Hello World的程序.在我们看printf函数的声明时,会看到类似于下面代码 int printf(const char * __restric ...

  7. golang 防SQL注入 基于反射、TAG标记实现的不定参数检查器

    收到一个任务,所有http的handler要对入参检查,防止SQL注入.刚开始笨笨的,打算为所有的结构体写一个方法,后来统计了下,要写几十上百,随着业务增加,以后还会重复这个无脑力的机械劳作.想想就l ...

  8. C语言不定参数

    最近,遇到一个c语言的不定参数问题.其实,对于c语言的不定参数问题,只需要三个函数就可以搞定了.这三个函数的头文件是<stdarg.h>,其实下面的三个函数都是一个宏定义(macro).  ...

  9. c++使用不定参数

    定义不定参数,使用的宏有: va_start(ap, arg)  初始化一个va_list的变量ap va_arg(ap, type)  获取下一个type类型的参数 va_end(ap)  结束使用 ...

随机推荐

  1. Facebook开源最先进的语音系统wav2letter++

    最近,Facebook AI Research(FAIR)宣布了第一个全收敛语音识别工具包wav2letter++.该系统基于完全卷积方法进行语音识别,训练语音识别端到端神经网络的速度是其他框架的两倍 ...

  2. [SP1043] GSS1 - Can you answer these queries I

    传送门:>Here< 题意:求区间最大子段和 $N \leq 50000$ 包括多组询问(不需要支持修改) 解题思路 线段树的一道好题 我们可以考虑,如果一组数据全部都是正数,那么问题等同 ...

  3. Codeforces510 C. Fox And Names

    Codeforces题号:#510C 出处: Codeforces 主要算法:判环+拓扑 难度:4.2 思路分析: 要是把这道题联系到图上就很容易想了. 如何建图?由于最后要求名字满足字典序,所以不妨 ...

  4. MT【296】必要性探路

    已知$a,b\in R.f(x)=e^x-ax+b$,若$f(x)\ge1$恒成立,则$\dfrac{b-a}{a}$的取值范围_____ 提示:答案:$[-1,\infty)$取$x=0,b\ge0 ...

  5. 【UOJ#246】套路(动态规划)

    [UOJ#246]套路(动态规划) 题面 UOJ 题解 假如答案的选择的区间长度很小,我们可以做一个暴力\(dp\)计算\(s(l,r)\),即\(s(l,r)=min(s(l+1,r),s(l,r- ...

  6. bzoj3514(LCT+主席树)

    题目描述 N个点M条边的无向图,询问保留图中编号在[l,r]的边的时候图中的联通块个数. 题解 对于一个截止时间来说,越晚的变越好. 所以我们可以维护一颗以边的序号为关键字的最大生成树,然后用主席树维 ...

  7. [WC2008]游览计划(状压dp)

    题面太鬼畜不粘了. 题意就是给一张n*m的网格图,每个点有点权,有k个关键点,让你把这k个关键点连成一个联通快的最小代价. 题解 这题nmk都非常小,解法肯定是状压,比较一般的解法插头dp,但不太好写 ...

  8. Gym - 100989F

    You must have heard about Agent Mahone! Dr. Ibrahim hired him to catch the cheaters in the Algorithm ...

  9. nuxt.js实战之开发环境配置

    一.创建项目 1.使用如下命令生成项目 vue init nuxt-community/starter-template testPro --testPro为项目名称 2.进入到项目根目录下,使用np ...

  10. sh: /etc/init.d/sshd: not found Docker中的Alpine镜像安装sshd无法启动

    问题描述 在Alpine镜像中安装了openssh-server和openssh之后,无法执行ssh localhost.发现未启动服务,开启服务时报以下错误 / # ls /etc/init.d/s ...