05 printf函式可變引數的實現原理之彙編分析
如實現一個像printf函式格式的函式:
test.c
void myprintf(char *line, ...) // line指標變數是區域性變數,在棧裡分配空間
{
printf(line); //呼叫printf時,r0存放字串地址
}
int main(void)
{
myprintf("hello test %d, %d, %s, %d, %c\n", 11, 22, "nono", 7788, 'K');
//呼叫myprintf時,共有6個引數, r0存放字串地址, r1存11, r2存22, r3存放"nono"字串地址, 後面兩個引數需存入棧裡
return 0;
}
在myprintf函式是如何知道引數的個數及如何取出引數的值?
呼叫myprintf函式時的第一個引數是一個字串的地址,通過遍歷字串裡的’%’字元,可以得知後面還帶有多少個引數。
引數值的獲取只能通過彙編來看了。
看彙編程式碼前,先回顧下:
在arm程式裡,引數是從r0暫存器開始傳引數,直到r3暫存器存放要傳遞的第四個引數,再多的引數就需壓棧了。
函式的引數也是區域性變數在棧裡分配空間。
反彙編得到的程式碼:
000083f4 <main>:
83f4: e92d4800 push {fp, lr}
83f8: e28db004 add fp, sp, #4
83fc: e24dd008 sub sp, sp, #8
8400: e59f302c ldr r3, [pc, #44] ; 8434 <main+0x40> //取出7788的值存入暫存器r3
8404: e58d3000 str r3, [sp] //把暫存器r3裡的值(7788)壓棧裡
8408: e3a0304b mov r3, #75 ; 0x4b // 字元'K'的ascii值存入暫存器r3
840c: e58d3004 str r3, [sp, #4] // 再把暫存器r3裡存放的值壓棧, 注意位置是(sp+4)
8410: e59f0020 ldr r0, [pc, #32] ; 8438 <main+0x44> //字串地址存入暫存器r0
8414: e3a0100b mov r1, #11 // r1存入11
8418: e3a02016 mov r2, #22 // r2存入22
841c: e59f3018 ldr r3, [pc, #24] ; 843c <main+0x48> //把"nono"字串的地址存入暫存器r3
8420: ebffffea bl 83d0 <myprintf> //呼叫myprintf
8424: e3a03000 mov r3, #0
8428: e1a00003 mov r0, r3
842c: e24bd004 sub sp, fp, #4
8430: e8bd8800 pop {fp, pc}
8434: 00001e6c andeq r1, r0, ip, ror #28 // 7788的十六進位制值存放在此
8438: 00008494 muleq r0, r4, r4
843c: 000084b4 // "nono"字串的地址是0x84b4
000083d0 <myprintf>:
83d0: e92d000f push {r0, r1, r2, r3}
83d4: e92d4800 push {fp, lr}
83d8: e28db004 add fp, sp, #4
83dc: e59b0004 ldr r0, [fp, #4] //r0存放 line指標變數指向的地址,由此可看出line指標變數它的地址是(fp+4)也就是(sp+8)
83e0: ebffffb7 bl 82c4 <_init+0x20> // printf(line);
83e4: e24bd004 sub sp, fp, #4
83e8: e8bd4800 pop {fp, lr}
83ec: e28dd010 add sp, sp, #16
83f0: e12fff1e bx lr
從main函式跳過來時,棧裡存放的內容順序:
['K' ]
[7788 ]
myprintf函式的前兩句壓棧後,棧裡存放的內容順序:
['K' ]
[7788 ]
["nono"字串的地址] // 由r3暫存器存放壓棧
[22 ] // r2暫存器
[11 ] // r1暫存器
&line--> ["hello."字串地址] // r0暫存器
[返回地址 ] // lr暫存器
sp--> [fp暫存器原內容 ] //最後棧頂在此記憶體單元位置
//注意,全部引數的地址都是連續的,只要獲取其中一個引數的地址,即可通過偏移獲取其它所有引數的值
//myprintf函式裡的指標變數的地址是在sp+8位元組的位置, 即&line + 4位元組即是引數11的地址.
///////////////////////////////////////////////////////////
測試引數取值的程式碼:
test.c
#include <stdio.h>
void myprintf(char *line, ...)
{
unsigned long *p = (unsigned long *)(&line);
printf("%s\n", *p++);
printf("%d\n", *p++);
printf("%d\n", *p++);
printf("%s\n", *p++);
printf("%d\n", *p++);
printf("%c\n", *p++);
}
int main(void)
{
myprintf("hello test %d, %d, %s, %d, %c\n", 11, 22, "nono", 7788, 'K');
return 0;
}
程式執行後的輸出結果:
^_^ /mnt # ./a.out
hello test %d, %d, %s, %d, %c
11
22
nono
7788
K
相關推薦
05 printf函式可變引數的實現原理之彙編分析
如實現一個像printf函式格式的函式: test.c void myprintf(char *line, ...) // line指標變數是區域性變數,在棧裡分配空間 { printf(line); //呼叫printf時,r0存放字串地
06 溢位攻擊原理之彙編分析
如c程式的程式碼: test.c: 1 2 #include <stdio.h> 3 4 int main(void) 5 { 6 int buf[10]
實現自己的printf列印 -- 可變引數的函式
/*X86平臺,引數傳遞是基於堆疊來完成的,對記憶體使用時連續的*/ void printf_myself(const char *format, ...) { //char *ptr_s = &format; int num; char
C列印函式printf的一種實現原理簡要分析
【0】README 【1】printf函式程式碼分析: P1)line66: va_list arg = (va_list)((char*)(&fmt) + 4); 要知道,對
通過可變引數實現函式,求函式的平均值
#include<stdio.h>#include<stdarg.h>#include<Windows.h>#pragma warning (disable :4996)int getaver(int num,...){va_list ar
dubbo實現原理之SPI簡介
ring 循環 -i OS ade ava rabl spi for循環 dubbo采用微內核+插件體系,設計優雅,擴展性很強。微內核+插件體系是如何實現的呢?想必大家都知道SPI(service provider interface)機制。這種機制的原理是假如我們定義
C++函式模板及實現原理
C++為我們提供了函式模板機制。所謂函式模板,實際上是建立一個通用函式,其函式型別和形參型別不具體指定,用一個虛擬的型別來代表。這個通用函式就稱為函式模板。 凡是函式體相同的函式都可以用這個模板來代替,不必定義多個函式,只需在模板中定義
stdarg.h 的使用 函式可變引數
全稱:standard arguments 成員: va_list :用來定義va變數,如va_list va。 va_start():使用方法:va_start(va,n) 其中va為va_list 定義的變數,n為引數個數。 va_arg() :使用方法:va_arg(va,typ
spring-AOP(二)實現原理之AspectJ註解方式
在上一篇spring-AOP(一)實現原理我們瞭解瞭如何使用ProxyFactory來建立AOP代理物件,但其過程需要實現一些介面,並且需要一些比較複雜的配置。因此,在spring2.0之後,提供了一種較為便利的方式。 使用@Aspect註解宣告一個切面類,之後通過@EnableAspectJAutoProx
JDK1.5特性——函式可變引數
我們在寫函式的時候,要往函式裡面傳遞引數進行運算。 public static int add(int a,int b){ return a+b; } public static int add(int a,int
Windows下DEVC++ 5.11 的printf函式對引數的執行順序
printf函式為其引數建立一個[棧],從右到左將引數壓入棧,再從棧內將裡面的元素依次列印。 函式舉例 #include <stdio.h> int p(int a) { print
java函式可變引數(不確定引數)的使用
java可變引數 當寫了一方方法後,想讓這個方法傳入不確定的引數值,就要用到可變引數 在jdk1.5加入了此方法,使用語法: 資料型別 ... 可變引數名稱 如:int ... data public class Test { public static v
Java多執行緒之AQS(AbstractQueuedSynchronizer )實現原理和原始碼分析(三)
章節概覽、 1、回顧 上一章節,我們分析了ReentrantLock的原始碼: 2、AQS 佇列同步器概述 本章節我們深入分析下AQS(AbstractQueuedSynchronizer)佇列同步器原始碼,AQS是用來構建鎖或者其他同步元件的基礎框架。
Java多執行緒之Condition實現原理和原始碼分析(四)
章節概覽、 1、概述 上面的幾個章節我們基於lock(),unlock()方法為入口,深入分析了獨佔鎖的獲取和釋放。這個章節我們在此基礎上,進一步分析AQS是如何實現await,signal功能。其功能上和synchronize的wait,notify一樣。
c語言 函式可變引數列表
1、編寫函式求一系列值的平均數,引數數目不確定: #include <stdarg.h> double average(int n_values, ...) { va_list var_arg; int count; double sum=0; va_st
coco2d-x中成員函式回撥實現原理
//標頭檔案 #ifndef __COOCS2D_CALLBACK_H__ #define __COOCS2D_CALLBACK_H__ #include <iostream> #include <string> using namespace std;
c++函式過載機制實現原理
一、c++函式過載的定義: 在同一作用域類,一組函式的函式名相同,引數列表不同(引數個數不同/引數型別不同),返回值可同可不同 二、函式過載的作用: 過載函式通常用來在同一個作用域內 用同一個函式名 命名一組功能相似的函式,這樣做減少了函式名的數量,避
如何用 linux 實現命令列引數(可變引數實現)
僅用main函式的引數實現一個整數計算器 #include <stdio.h> #include <string.h> #include <stdlib.h>
C語言可變引數的原理
轉自:http://blog.csdn.net/bigloomy/article/details/6588354 這個寫得比較簡單,明瞭,看了這個才真正理解了變長引數怎麼實現的。 在學習C語言的過程中我們可能很少會去寫變參函式,印象中大學老師好像也沒有提及過,但我發現變參
Linux下函式可變引數va_arg_##__VA_ARGS__巨集
va_list //compile:gcc va_arg.c //run:./a.out //Notes:如果遇到獲取char,type用int,如果該用char會提示...傳遞時被提升為int。此處使用的編譯器是Linux下的gcc #include<stdio.