成功的企业网站案例,微信对接网站,wordpress资源网主题,网站怎么做聚合1、一个简单的函数 
int Add(int x, int y)
{return x  y;
}int main()
{printf(%d, Add(2, 3, 4, 5, 6));return 0;
} 这一段足够简单的代码#xff0c;闭眼都能知道运行结果会在屏幕上打印 5 。那编译器是怎么处理后面的 4、5、6 #xff1f; 我们再看看这个函…1、一个简单的函数 
int Add(int x, int y)
{return x  y;
}int main()
{printf(%d, Add(2, 3, 4, 5, 6));return 0;
} 这一段足够简单的代码闭眼都能知道运行结果会在屏幕上打印 5 。那编译器是怎么处理后面的 4、5、6  我们再看看这个函数。 
void MyTest(int a, int b, int c, int d)
{printf(%d, a);
} 似乎参数 b、c、d 的设定是多余的。不论这三个参数传入什么值都不影响结果。那上述的 Add 函数是不是也能看作后续 4、5、6 对应的参数没有用到所以没有表现出任何现象 带着这个问题再看一个函数 
void MyTest_2(int num)
{int* ptr  num;for (int i  0; i  num; i){printf(%d , *ptr);ptr;}
} 是不是有点懵对形参取地址是个什么操作还要对形参的指针进行移动再打印出来又是什么鬼预感上大概率会报非法访问。 那不妨我们在 main 函数里调用一下 
MyTest_2(0);
MyTest_2(1);
MyTest_2(10); 然而结果是 虽然返回了一堆不明所以的值但返回代码 0 说明程序压根就没有报错。而参数 0 、 1 、 10 都被完整打印出来了简直是毁三观。 知道你很急但是你先别急再在 main 函数中试试这个足以让人懵逼的例子 
MyTest_2(5, 100, 20, 35, 40, 114514); 结果更毁三观了  什么玩意明明 MyTest 创建时只设定了一个参数为什么传入六个参数能全部打印出来是不是说明一开始的 Add 调用后面的 4、5、6 也一并进行了传参只是没有在函数内部进行使用 要弄懂这个问题首先得了解编译器对函数调用时的参数是怎么处理传参过程又是怎么样的。 
2、函数传参过程 
2.1、栈帧建立之前 调用函数时系统会在内存中创建对应函数的栈帧。关于栈帧建立及销毁这部分内容可以看这一篇开头部分函数栈帧简述。 而在进行栈帧建立之前程序还会执行一系列的操作。以这段代码为例直接在汇编中看看 ret 赋值时的 Add 调用汇编指令到底做了什么 
int Add(int x, int y)
{return x  y;
}int main()
{int ret  Add(0xAB, 0xCD, 0xEF, 0xAA, 0xDD);return 0;
} 当前栈帧是 main 函数根据以上汇编指令在调用 Add 函数之前程序将 0xAB、0xCD、0xEF、0xAA、 0xDD 这五个参数逆序放入 main 函数栈顶 ESP 是栈顶寄存器。 这一步实际上就是函数传参。通过这一步得出结论不论函数在创建时定义了多少个形参甚至不定义形参只要在调用时函数名后的括号内写入参数就一定会进行传参。 
2.2、参数调用 上述两句汇编代码首先是将 ebp8 位置的值存入 eax 寄存器再让 eax 寄存器中的值   ebp12 位置的值。而 ebp8 的地址与 ebp12 的地址分别储存了 0xAB 和 0xCD 。 至此就是一次完整的传参及参数调用。  也就是说只需要知道第一个参数的地址那么剩下的参数即使不在创建函数时定义也可以通过第一个参数的地址进行访问。就此最开始的 MyTest_2 函数产生的现象也就解释完毕。 
3、可变参数列表 
3.1、定义阐述 严格来说 C 语言的函数参数数量并不是固定的那么在应用上根据传入的各个参数类型及第一个参数的地址对函数传入任意参数个数只需要通过某种方式在函数内部进行调用那么函数的灵活性和扩展性就大大提高了。 很好 printf 也是这么想的。在使用 printf 时第一个参数中有几个占位符后续就带几个参数各位对这规则应该已经形成肌肉记忆了。而对于之前的 MyTest_2 函数唯一定义的参数便是后续传入有效参数的个数。而为了语义上更加直观像这类可对后续参数进行操作的函数在创建时一般会加上三个点。当然也是为了语义将变量名改为 argc argument count 
void MyTest_2(int argc, ...)
{int* ptr  argc;for (int i  0; i  argc; i){printf(%d , *ptr);ptr;}
} 如以上函数中用其中某个参数确定后续参数的个数那么这一系列参数就叫可变参数列表。 
3.2、初步实现 虽然在 MyTest_2 中已经初步实现了带可变参数列表的函数创建但这个函数好像没什么用。所以这里再举一个例子求若干浮点数的和 
double Sum(int argc, ...)
{double sum  0.;//创建可变参数列表的头部指针将指针指向列表第一个元素double* ptr  (double*)(argc  1);//遍历可变参数列表求和for (int i  1; i  argc; i){sum  *ptr;//指针指向下一个参数ptr;}return sum;
} 这代码貌似没问题但传入的参数列表仅限于 double 类型如果传入的参数是一个整型变量呢由于内部只能通过指针访问根本无法知晓外部传入的变量类型而且编译器也不会对可变参数列表中的参数类型作检查。 所以如果列表的参数类型不一致第一个参数除了附带参数的数量信息外还应附带每个参数的类型。解决办法可以参照 printf 的第一个参数。在此之前先了解一个点函数在传参时汇编指令会对参数进行类型提升和 4 字节对齐。也就是说char、short 的类型会被提升为 int 而 float 类型直接提升为 double 。 修改后如下 
//format字符串只允许d或f不区分大小写
double Sum(const char* format, ...)
{double sum  0.;int count  strlen(format);//创建可变参数列表的头部指针将指针指向列表第一个元素char* ptr  (char*)(format)  sizeof(char*);for (int i  0; i  count; i){//遇到字符d或者D以整型处理if (format[i]  d || format[i]  D){sum  (double)*((int*)ptr);//指针指向下一个参数ptr  sizeof(int);}//遇到字符f或者F以双精度浮点型处理else if (format[i]  f || format[i]  F){sum  *((double*)ptr);//指针指向下一个参数ptr  sizeof(double);}}return sum;
} 至此已经很接近 printf 的参数调用方式了。 
3.3、可变参数列表宏 调用 stdio.h 头文件便可以使用专用于处理可变参数列表的四个宏 va_list用于创建读取可变参数列表的指针 
typedef char* va_list; __crt_va_start将可变参数列表的指针指向列表第一个参数 
#define __crt_va_start_a(ap, v) ((void)(ap  (va_list)_ADDRESSOF(v)  _INTSIZEOF(v)))
#define __crt_va_start(ap, x) __crt_va_start_a(ap, x) __crt_va_arg获取可变参数列表的指针当前指向的参数并将指针指向下一个参数 
#define __crt_va_arg(ap, t) (*(t*)((ap  _INTSIZEOF(t)) - _INTSIZEOF(t))) __crt_va_end用于销毁可变参数列表的指针。 
#define __crt_va_end(ap) ((void)(ap  (va_list)0)) 此外对上述 _INTSIZEOF 和 _ADDRESSOF 也需要作了解 
#define _ADDRESSOF(v) ((v))
#define _INTSIZEOF(n) ((sizeof(n)  sizeof(int) - 1)  ~(sizeof(int) - 1)) 上述的 _INTSIZEOF 比较难以理解。它的运算结果是 4 字节对齐这个公式有点巧妙有兴趣可以自行理解。 接下来先将上面的代码用这几个宏改造一下 
double Sum(const char* format, ...)
{double sum  0.0;va_list ptr;__crt_va_start(ptr, format);for (int i  0; i  strlen(format); i){if (format[i]  d || format[i]  D){sum  __crt_va_arg(ptr, int);}else if (format[i]  f || format[i]  F){sum  __crt_va_arg(ptr, double);}}__crt_va_end(ptr);return sum;
} 不过这几个宏不推荐使用因为随着编译器的不同很可能某些编译器并不支持这些宏可移植性大大降低。这里主要是提供宏的思路至于宏的实现也已经展示各位完全可以根据这些宏通过纯 C 代码实现。