printf不定引數
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型別傳遞
使用不定引數
- 獲取定參地址,首地址
- 對連續地址進行依次取值,移動指標到下一個引數的地址
確定不定引數個數
- 在型別固定的引數中指明後面有多少個引數以及他們的型別。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
[email protected]:~/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
[email protected]:~/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