基础知识:其实参数传入函数的时候,其实实际传入的是一个堆中连续内存区的首地址(函数在栈中),里面以传入的顺序放入了用户传递的参数。
而C语言有一种叫“变参函数”的特性方便用户传入不定数量的参数以方便用户进行某些如“字符串拼接”函数等功能的编写。
其中要注意内存对齐的问题,例子中的currentParams是指向参数内存区地址的指针,所以读取的时候要注意做好指针类型转换,转换完之后以便实现内存对齐后的正确取值。先把void*指针转化为一个确定类型的指针如doublue*,再在前面加*即可取得对应内容,如*((double*)currentParams)。
而有些地方取出的整数其实是一个地址,例如字符串传入的时候其实传入的是字符数组连续内存区的首地址,所以得到这个地址之后,例如得到了*(int*)currentParams这个整数,已知这个整数是传入的一个字符数组的连续内存区的首地址,我想通过printf输出,因此需要把这个整数代表的地址转换为一个char*指针——(char*)(*(int*)currentParams)才可以被printf顺利识别为一个字符串。其实指针保存的都是内存地址这种整数,不同类型的指针都是这样,只是有了类型声明,方便程序读内容时对齐内存,以免如4个字节的int类数据被读了一个字节出来,也避免了4个字节一个单元的int数组被以8个字节一组的方式(如double类型、struct{int a, int b}型)读错。
而C语言有一种叫“变参函数”的特性方便用户传入不定数量的参数以方便用户进行某些如“字符串拼接”函数等功能的编写。
其中要注意内存对齐的问题,例子中的currentParams是指向参数内存区地址的指针,所以读取的时候要注意做好指针类型转换,转换完之后以便实现内存对齐后的正确取值。先把void*指针转化为一个确定类型的指针如doublue*,再在前面加*即可取得对应内容,如*((double*)currentParams)。
而有些地方取出的整数其实是一个地址,例如字符串传入的时候其实传入的是字符数组连续内存区的首地址,所以得到这个地址之后,例如得到了*(int*)currentParams这个整数,已知这个整数是传入的一个字符数组的连续内存区的首地址,我想通过printf输出,因此需要把这个整数代表的地址转换为一个char*指针——(char*)(*(int*)currentParams)才可以被printf顺利识别为一个字符串。其实指针保存的都是内存地址这种整数,不同类型的指针都是这样,只是有了类型声明,方便程序读内容时对齐内存,以免如4个字节的int类数据被读了一个字节出来,也避免了4个字节一个单元的int数组被以8个字节一组的方式(如double类型、struct{int a, int b}型)读错。
以下是读取变参函数变量的一个例子,这套代码可以稍加修改加上一些汇编代码,实现如输出内容到串口设备等的自定义printf函数。也可以说printf函数就是一种最常用的变参函数之一:
#include "stdio.h" #include "stdlib.h" #include "string.h" void testMultiParams(char *paramsFormat, ...){ int paramsLength = 0, i, j = 0; /*不能拿paramsFormat加偏移地址拿参数,paramsFormat存的地址是那堆字符的连续内存区的首地址,不是参数的首地址 所以要拿指向这个连续内存区的首地址的数据存储区的地址(向“根”寻址——找到指向这个地址的地址)*/ void* firstParamsAddress = (void*) (¶msFormat + sizeof(char)); void* currentParams = firstParamsAddress; for(i = 0; i < strlen(paramsFormat); i++){ if(paramsFormat[i] == '%') paramsLength++; } printf("参数长度%d\n", paramsLength); for(i = 1; i <= strlen(paramsFormat); i++){ if(paramsFormat[i - 1] == '%') { switch(paramsFormat[i]){ case 'd': printf("第%d参数内容是:%d\n", ++j, *((int*)currentParams)); currentParams+=sizeof(int); break; case 'f': printf("第%d参数内容是:%f\n", ++j, *((double*)currentParams)); currentParams+=sizeof(double); break; case 'c': printf("第%d参数内容是:%c\n", ++j, ((char*)currentParams)[0]); currentParams+=sizeof(char*); break; case 's': /*字符串形参数是保存着指向一个“字符数组首地址”这个整数的地址,取得该地址, 并取得地址中保存了的内容——“字符数组首地址”这个地址整数,然后转为字符串地址 (向“叶”寻址——地址的地址)*/ printf("第%d参数内容是:%s\n", ++j, (char*)(*(int*)currentParams)); currentParams+=sizeof(char*); break; case 'l': if(paramsFormat[i+1] == 'd'){ printf("第%d参数内容是:%ld\n", ++j, *(long*)currentParams); currentParams+=sizeof(long); i++; } break; default: printf("第%d参数类型标记错误,正在跳过...\n", j+1); while(paramsFormat[++i] != '%'); currentParams+=sizeof(void*); ++j; break; } } } } int main(){ testMultiParams("%d", 123); testMultiParams("%d%d", 123, 345); testMultiParams("%d%s%d", 123, "asdasdasd", 678); testMultiParams("%d%s%d%ld%s", 123, "变参函数测试", 678, 678999999L, "模仿Java多态"); testMultiParams("%d%s%d%yyy%s", 3, "错误情况测试", 678, 678999999L, "测试"); printf("%.2f\n", 3.1415f); testMultiParams("%d%s%c%f%ld%s", 3, "测试", 'b', 3.1415f, 678999999L, "测试"); getchar(); return 0; }
进阶用法,字符串拼接例子:
#include "stdio.h" #include "stdlib.h" #include "string.h" /**数组长度判断宏**/ #define ARR_LENGTH(arr) sizeof(arr)/sizeof(arr[0]) char* stringAllPlus(int paramsCount, ...){ void* firstParamsAddress = (void*) (¶msCount + 1); void* currentParams = firstParamsAddress; char* totalStr = NULL; char* cursorPointer = NULL; int i, j, totalStrLength = 0, offset = 0; /*统计传入的字符串总长度*/ for(i = 0; i < paramsCount; i++){ totalStrLength += strlen((char*)(*(int*)currentParams)); currentParams+=sizeof(char*); } /*恢复游标到连续地址空间的头地址*/ currentParams = firstParamsAddress; /*printf("Total String length:%d\n", totalStrLength);*/ /*拼接成一个字符串*/ if(totalStrLength <= 0) return NULL; totalStr = cursorPointer = (char*) malloc(sizeof(char) * totalStrLength + 1); memset((void*)cursorPointer, 0, totalStrLength); for(i = 0; i < paramsCount; i++){ memcpy((void*)cursorPointer, (void*)(*(int*)currentParams), strlen((char*)(*(int*)currentParams))); cursorPointer+=strlen((char*)(*(int*)currentParams)); currentParams+=sizeof(char*); } totalStr[totalStrLength] = '\0'; return totalStr; } void stringAllPlus2(int paramsCount, ...){ char* firstParamsAddress = (char*) (¶msCount + 1); printf("%s\n", ((int*)firstParamsAddress)[0]); printf("%s\n", ((int*)firstParamsAddress)[1]); } char* stringAllPlus3(int paramsCount, ...){ int* paramsAddress = (int*) (¶msCount + 1); char* cursorPointer = NULL; char* totalStr = NULL; int i, totalStrLength = 0; /*统计传入的字符串总长度*/ for(i = 0; i < paramsCount; i++) totalStrLength += strlen((char*) paramsAddress[i]); /*拼接成一个字符串*/ if(totalStrLength <= 0) return NULL; totalStr = malloc(totalStrLength + 1); cursorPointer = totalStr; memset(cursorPointer, 0, totalStrLength); for(i = 0; i < paramsCount; i++){ memcpy((void*) cursorPointer, (void*) paramsAddress[i], sizeof((char*) paramsAddress[i])); cursorPointer += strlen((char*) paramsAddress[i]); } totalStr[totalStrLength] = '\0'; return totalStr; } void test(int a, ...){ int* arr = &a; void* arr2 = (void*)&a; printf("%d,%d,%d\n", *(int*)(&a), *(int*)(&a + 1), *(int*)(&a + 2)); printf("%d\n", ((int*)&a)[0]); printf("%d\n", ((int*)&a)[1]); printf("%d\n", ((int*)&a)[2]); printf("%d\n", arr[0]); printf("%d\n", arr[1]); printf("%d\n", arr[2]); printf("%d\n", ((int*)arr2)[0]); printf("%d\n", ((int*)arr2)[1]); printf("%c\n", ((char*)arr2)[2]); //错误用法,内存对齐错误 } int main(){ char* temp = stringAllPlus3(3, "abc", "sdfsdfsdf\n", "fghhhhhhhhh"); char* totalStr[] = {stringAllPlus3(3, "abc", "哈哈哈", "fgh"), stringAllPlus3(2, "you ", "suck")}; char* temp2 = ""; int i; printf("total str:%s\n", temp); printf("total str:%s\n", totalStr[0]); printf("total str:%s\n", totalStr[1]); for(i = 'a'; i <= 'z'; i++){ temp2 = stringAllPlus(2, temp2, &i); } printf("%s\n", temp2); test(33, 55, 97); stringAllPlus2(2, "you ", "suck"); return 0; }