基础知识:其实参数传入函数的时候,其实实际传入的是一个堆中连续内存区的首地址(函数在栈中),里面以传入的顺序放入了用户传递的参数。
而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;
}