網路城邦
上一篇 回創作列表 下一篇   字體:
《教學》不定參數的 C 函式
2007/02/01 14:21:48瀏覽8270|回應11|推薦7
只要寫過 C 程式的人,都用過 printf 這個函式,也都知道它可是一個不定參數的函式,了解它的運作方式,你也可以實作自己的不定參數的函式。

先看一下 printf 的原型:int printf( const char *format [, argument]... );
在format後面的參數不但不定個數且型態也是不定的。這些參數是one bye one 的堆在stack裡面。
printf這個函式如何正確的取出這些參數?秘訣就在 format,函式根據format裡的格式("%d %f...")
依序把堆在stack裡的參數取出。而這取出的動作就是用到va_arg, va_end, va_start這三個macro再加上va_list。va_list 事實上是一個 char * 的型態:
typedef char * va_list;

其它的三個macro的例子(不同的平台會有不太一樣的macro)
/* A guess at the proper definitions for other platforms */
#define _INTSIZEOF(n) ( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) )

#define va_start(ap,v) ( ap = (va_list)&v + _INTSIZEOF(v) )
#define va_arg(ap,t) ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) )
#define va_end(ap) ( ap = (va_list)0 )

這三個macro若看得懂,對了解它們的運作會很有幫助;看不太懂也沒關係,看一下下面的說明也差不可以了。首先 va_start 把取出的指標(va_list)指到第一個不定參數。
然後就可以用 va_arg 以指定的型態從va_list取出資料並把va_list指標移到下一個位置(例如取走了一個int 4 bytes 的資料, va_list便會加 4)
當取完資料後便可使用 va_end 把 va_list 歸零(這個macro應看得懂吧)。
其實對大部份的應用來說 va_end 做不做是沒太大的關係的。

下面是一個不定參數函式的範例(轉載自MSDN):
int average( int first, ... )
{
int count = 0, sum = 0, i = first;
va_list marker;

va_start( marker, first ); /* Initialize variable arguments. */
while( i != -1 )
{
sum += i;
count++;
i = va_arg( marker, int);
}
va_end( marker ); /* Reset variable arguments. */
return( sum ? (sum / count) : 0 );
}

註:_INTSIZEOF 是 int size memory alignment,如果你不知那是什麼就把 _INTSIZEOF 看成這樣的定義會比較容易理解:
#define _INTSIZEOF(n) sizeof(n)
為什麼不直接用sizeof(n)就好了,使用 macro 是為了跨平台時能保持運作正確。若傳了一個2 bytrs的不定參數(如 short),但在一個 32 bits的平台推入堆疊就會是4個bytes(32 bits)不是2 bytes,所以參數的取出就要非常的小心。使用_INTSIZEOF 在於保證引數指標移動的正確。
( 休閒生活旅人手札 )
回應 推薦文章 列印 加入我的文摘
上一篇 回創作列表 下一篇

引用
引用網址:https://classic-blog.udn.com/article/trackback.jsp?uid=keithmin&aid=706363

 回應文章 頁/共 2 頁  回應文章第一頁 回應文章上一頁 回應文章下一頁 回應文章最後一頁

陳小春
等級:6
留言加入好友
BOOKMARK
2007/02/13 17:09
http://cha.homeip.net/blog/archives/2006/12/linux_kernel_v2.html

http://chongbaoxia.linux-fans.com/category/%E5%B5%8C%E5%85%A5%E5%BC%8F%E5%BA%94%E7%94%A8/

http://home.educities.edu.tw/wanker742126/asm/ap10.html

http://people.linux.org.tw/~pofeng/webime/py-tw.html

http://www.debian.org/international/Chinese/download

http://wiki.linux.org.hk/w/How_to_make_Debian_support_chinese

http://cle.linux.org.tw/trac/wiki/GcinTetraletArticle?format=txt

http://jserv.sayya.org/qtopia/doc/qpe-devel/

http://blog.linux.org.tw/~jserv/archives/001280.html

http://blog.linux.org.tw/~jserv/archives/001859.html

http://doc.trolltech.com/3.3/

http://www.study-area.org/tips/qd/qt.html

http://www.qtopia.org.cn/phpBB2/index.php?sid=d9433768025c47d4f1f01797861fe1c3

http://jserv.sayya.org/qtopia/doc/qpe-devel/

陳小春
等級:6
留言加入好友
Perl教學
2007/02/08 02:40
http://www.cyut.edu.tw/~hcchen/perl/Perl%20tutorial%20Start.htm

陳小春
等級:6
留言加入好友
Perl教學
2007/02/08 02:35
http://linux.tnc.edu.tw/techdoc/perl_intro/

陳小春
等級:6
留言加入好友
可以執行的
2007/02/02 03:15
#include
#include
#include

typedef struct{
char *logheader;
}AppData;

int App_Log(AppData* app, char* format, ...){
static char log[100];
va_list args;
int ret;
char* kkbox;
va_start(args, format);
// vsprintf(log, format, args);
//ret = vfprintf( app->fp , "[%s]%s", app->statename, log);
// va_arg( args, int);//這個一定要設定,才能把參數設定型別~後面int or int* or ...都OK
kkbox = va_arg( args, char*);
//printf("%s %s",format,kkbox);
ret = printf("%s %s",log,app->logheader);
va_end(args);
//printf("gggggg%s\n",app->logheader);
return ret;//kkbox
}

char* test()
{
printf("testtesttesttesttesttesttesttesttesttesttesttest \n");
return "fucj";
}


int main()
{ AppData *app;
app->logheader=(char*)test();//average("avg", "DDD", 123L, 23.0, "10", "20");
printf("HHHH\n");
App_Log( app,"test()\n");
//printf("average=%d\n", App_Log( app, "test()\n"));
return 0;
}

陳小春
等級:6
留言加入好友
va_arg
2007/02/01 22:10
#include
#include /* for printf */

int varfunc (char *buf, int id, ...) {
va_list tag;
va_start (tag, id);
if (id == 0) {
int arg1;
char *arg2;
long arg3;
arg1 = va_arg (tag, int);
arg2 = va_arg (tag, char *);
arg3 = va_arg (tag, long);
}

else {
char *arg1;
char *arg2;
long arg3;

arg1 = va_arg (tag, char *);
arg2 = va_arg (tag, char *);
arg3 = va_arg (tag, long);
}
va_end (tag);
}

void caller (void) {
char tmp_buffer [10];
varfunc (tmp_buffer, 0, 27, "Test Code", 100L);
varfunc (tmp_buffer, 1, "Test", "Code", 348L);
}
http://www.keil.com/support/man/docs/c166/c166_va_arg.htm

陳小春
等級:6
留言加入好友
2007/02/01 18:52
http://www.elook.org/programming/c/printf.html

http://topic.csdn.net/t/20020421/15/664474.html

http://topic.csdn.net/t/20031116/13/2462781.html

陳小春
等級:6
留言加入好友
錯誤原因
2007/02/01 15:01
Q2在 VC 一執行就會出現錯誤的視窗,要我關閉 or 偵錯的那個;
而在 TC 中執行的結果如下:
[10]
[20]
[39]
average = 0
不知道為什麼在 VC 就是不能跑,用 Debug 的話,會顯示出一段組語:

dodwords:
mov eax,[edx]

cmp al,[ecx]
jne short donene
or al,al
jz short doneeq
cmp ah,[ecx + 1]
jne short donene
or ah,ah
jz short doneeq

A:while (strcmp(buffer = va_arg(marker, char *), NULL))本來就是一個錯誤的敘述!
在保護模式下,strcmp去取存 NULL 指標內容會產生 access exception
在 TC 可以跑是因TC是在 real mode 的 DOS 下跑。
從程式的意思應改成這樣:
while (buffer = va_arg(marker, char *) //buff 在等於NULL 時會跳出迴圈

陳小春
等級:6
留言加入好友
測試
2007/02/01 14:59
int average(char *first, char *second, long ii, double mm, ...)
{
int count = 0, sum = 0, i;
char *buffer;
va_list marker;
va_start(marker, mm);
while (strcmp(buffer = va_arg(marker, char *), NULL))
printf("[%s]\n", buffer);
va_end(marker); /* Reset variable arguments. */
return (sum ? (sum / count) : 0);
}

void main()
{
clrscr();
printf("average=%d\n", average("avg", "DDD", 123L, 23.0, "10", "20", "39", NULL));
}

code:
#include
#include
#include

int cdecl MessageBoxPrintf(TCHAR* szCaption, TCHAR* szFormat, ...)
{
TCHAR szBuffer[1024];
va_list pArgList;
va_start(pArgList, szFormat);
_vsntprintf(szBuffer, sizeof(szBuffer) / sizeof(TCHAR), szFormat, pArgList);
va_end(pArgList);
return MessageBox(NULL, szBuffer, szCaption, 0);
}

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow)
{
int cxScreen, cyScreen;
cxScreen = GetSystemMetrics(SM_CXSCREEN);
cyScreen = GetSystemMetrics(SM_CYSCREEN);
MessageBoxPrintf(TEXT("ScrnSize"), TEXT("The screen is %i pixels wide by %i pixels high."),
cxScreen, cyScreen);
return 0;
}

陳小春
等級:6
留言加入好友
概數問題吧!?
2007/02/01 14:53
有n個蘋果,無條件進位取x的倍數,會是幾個蘋果?
A. (n+(x-1)) - ((n+(x-1))%x)
如果x是2的m次方(x=1,2,4,8...),那麼上面的A.公式可簡化為:B. (n+(x-1)) & ~(x-1)
因為 :令 N=(n+(x-1)) 那麼 A.公式可以說成N減去N除以x的餘數
如果x是2的幾次方(1,2,4,8...),N減去N除以x的餘數可以寫成:& ~(x-1)

why?
x=1-->N減去N除以1的餘數= N & (0xFF..FF)= N & ~(1-1)

x=2-->N減去N除以2的餘數= N & (0xFF..FE)= N & ~(2-1)

x=4-->N減去N除以4的餘數= N & (0xFF..FC)= N & ~(4-1)

::::::::::::::::::::::::::::::::::
最後把B.公式以 n代入sizeof(n), x代入sizeof(int)就得到
C. (sizeof(n)+(sizeof(int)-1)) & ~(sizeof(int)-1)
這個解釋是不是老太婆的纏腳布又臭又長?這是為什麼我沒在該篇文章說明的原因。
_INTSIZEOF(n) 目的是要得到參數n無條件進位取sizeof(int)的倍數。
為什麼是無條件進位取sizeof(int)的倍數?因為這是一個較通用版本的macro,一般的編譯器的定的int 型態的大小和堆疊存取的單位是相同的(但不是絕對的,所以才又有特殊版本)

陳小春
等級:6
留言加入好友
為什麼 va_start 的第2個引數只要傳入【最後一個固定參數】就可以
2007/02/01 14:50
為什麼 va_start 的第2個引數只要傳入【最後一個固定參數】就可以
移到第1個不定參數上。
va_list通常是定義成 (char *),所以:
#define va_start(ap,v) ( ap = (char *)&v + sizeof(v))
對一個 有 3 個固定參數的不定參數函式:
void Test( type1 Parm1, type2 Parm2, type3 Parm3, ...){
va_list pArgList;
va_start(pArgList, Parm2);//pArgList=(char *)&Parm2 + sizeof(Parm2)
//pArgList=(char *)&Parm2 + sizeof(Parm2)
//將 Parm2 的位址取出後,轉型成 (char *),在加上 Parm2 的大小,這樣子在記憶體中就會移到 Parm3 了“所以現在 pArgList 是指向 Parm2 的下一個參數 Parm3
va_start(pArgList, Parm1);//pArgList=(char *)&Parm1 + sizeof(Parm1)
//所以現在 pArgList 是指向 Parm1 的下一個參數 Parm2
va_start(pArgList, Parm3);//pArgList=(char *)&Parm3 + sizeof(Parm3)
//所以現在 pArgList 是指向 Parm3 的下一個參數(第一個不定參數)
}
頁/共 2 頁  回應文第一頁 回應文章上一頁 回應文章下一頁 回應文章最後一頁