函数栈帧与可变参数列表 p1可变参数列表(完结)( C语言从入门到入土(进阶篇)

Posted 原来45

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了函数栈帧与可变参数列表 p1可变参数列表(完结)( C语言从入门到入土(进阶篇)相关的知识,希望对你有一定的参考价值。

目录

可变参数列表

1. 求两个数据中的最大值

2. 如果将参数改成char类型,求char类型变量中的最大值,代码会有问题吗?

3. 结果并未受影响,可是,我们解析的时候,是按照va_arg(arg, int)来解析的,这是为什么?

4. 注意事项

5. 原理

6. 先看看这几个宏的含义

7. 原理图


本章节文章是作者通过观看《C语言深度剖析》等各种资料总结的精华,基础部分省略了不少,是为了让大家能够更加深入了解C语言的魅力!因为为了避免与之前的文章发生赘述,所以就直接讲作者认为的精华部分哈!现在正文开始! 

谁都不能阻挡你成为更优秀的人。  

在这里《C语言深度剖析》就结束了哈,作者后面可能会把前面的各个文章再总结一下,让大家更好地去查阅,接下来就慢慢踏入数据结构了哈!作者也期待很久了,让我们一起进步哈! 

可变参数列表

1. 求两个数据中的最大值

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <windows.h>
int FindMax(int x, int y)

        if (x > y)
        return x;
    
        return y;

int main()

        int x = 0;
        int y = 0;
        printf("Please Eneter Two Data# ");
        scanf("%d %d", &x, &y);
        int max = FindMax(x, y);
        printf("max = %d\\n", max);
        system("pause");
        return 0;
求任意多个数据中的最大值 ( 至少一个 ) ,要求不能使用数组
因为目前参数个数不确定,那么函数编写的时候,参数个数也无法确定,换句话说,函数也就没法编写
不过, C 提供了满足该场景的解决方案:可变参数列表
#include <stdio.h>
#include <windows.h>
//num:表示传入参数的个数
int FindMax(int num, ...)

        va_list arg; //定义可以访问可变参数部分的变量,其实是一个char*类型
        va_start(arg, num); //使arg指向可变参数部分
        int max = va_arg(arg, int); //根据类型,获取可变参数列表中的第一个数据
        for (int i = 0; i < num - 1; i++)//获取并比较其他的
        int curr = va_arg(arg, int);
        if (max < curr)
        max = curr;
        

        va_end(arg); //arg使用完毕,收尾工作。本质就是讲arg指向NULL
        return max;

int main()

        int max = FindMax(5,11,22,33,44,55);
        printf("max = %d\\n", max);
        system("pause");
        return 0;

1. 如果将参数改成char类型,求char类型变量中的最大值,代码会有问题吗?

#include <stdio.h>
#include <windows.h>
//num: 表示传入参数的个数
int FindMax ( int num , ...)
        va_list arg ; // 定义可以访问可变参数部分的变量,其实是一个 char* 类型
        va_start ( arg , num ); // 使 arg 指向可变参数部分
        int max = va_arg ( arg , int ); // 根据类型,获取可变参数列表中的第一个数据
        for ( int i = 0 ; i < num - 1 ; i ++ ) // 获取并比较其他的
        int curr = va_arg ( arg , int );
        if ( max < curr )
        max = curr ;
                
        
        va_end ( arg ); //arg 使用完毕,收尾工作。本质就是讲 arg 指向 NULL
        return max ;
int main ()
        char a = '1' ; //ascii : 49
        char b = '2' ; //ascii : 50
        char c = '3' ; //ascii : 51
        char d = '4' ; //ascii : 52
        char e = '5' ; //ascii : 53
        int max = FindMax ( 5 , a , b , c , d , e );
        printf ( "max = %d\\n" , max );
        system ( "pause" );
        return 0 ;

2. 结果并未受影响,可是,我们解析的时候,是按照va_arg(arg, int)来解析的,这是为什么?

通过查看汇编,我们看到,在可变参数场景下:
1. 实际传入的参数如果是char,short,float,编译器在编译的时候,会自动进行提升(通过查看汇编,我们都能看到)
2. 函数内部使用的时候,根据类型提取数据,更多的是通过int或者double来进行

 

2. 注意事项

1.可变参数必须从头到尾逐个访问。如果你在访问了几个可变参数之后想半途终止,这是可以的,但是,如果你想一开始就访问参数列表中间的参数,那是不行的。
2.参数列表中至少有一个命名参数。如果连一个命名参数都没有,就无法使用 va_start 。
3.这些宏是无法直接判断实际存在参数的数量。
4.这些宏无法判断每个参数的是类型。
5.如果在 va_arg 中指定了错误的类型,那么其后果是不可预测的。

3. 原理

1. 可变参数列表对应的函数,最终调用也是函数调用,也要形成栈帧
2. 栈帧形成前,临时变量是要先入栈的,根据之前所学,参数之间位置关系是固定的
3. 通过上面汇编的学习,发现了短整型在可变参数部分,会默认进行整形提升,那么函数内部在提取该数据的时候,就要考虑提升之后的值,如果不加考虑,获取数据可能会报错或者结果不正确

4. 先看看这几个宏的含义

//va_list其实就是char*类型,方便后续按照字节进行指针移动
typedef char * va_list;
#define va_start _crt_va_start
#define va_arg _crt_va_arg
#define va_end _crt_va_end
//这个宏特别好理解,结合栈帧中临时参数的压入位置
#define _crt_va_start(ap,v) ( ap = (va_list)_ADDRESSOF(v) + _INTSIZEOF(v) )
//这个设计特别巧妙,先让ap指向下个元素,然后使用相对位置-偏移量,访问当前元素。
//访问了当前数据的同时,还让ap指向了后续元素,一举两得。
#define _crt_va_arg(ap,t) ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) )
//这个宏特别好理解,将ap指针设置为NULL
#define _crt_va_end(ap) ( ap = (va_list)0 )
//取参数的地址,也很好理解
#define _ADDRESSOF(v) ( &(v) )
//难点是这个,不太好理解
#define _INTSIZEOF(n) ( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) )
前提:
为了后面方便表述,我们假设sizeof(n)的值是n(char 1,short 2, int 4)
我们在32位平台,vs2013下测试,sizeof(int)大小是4,其他情况我们不考虑
_INTSIZEOF(n)的意思:计算一个最小数字x,满足 x>=n && x%4==0,其实就是一种4字节对齐的方式是什么:
比如n是:1,2,3,4 对n进行向 sizeof(int) 的最小整数倍取整的问题 就是 4
比如n是:5,6,7,8 对n进行向 sizeof(int) 的最小整数倍取整的问题 就是 8
为什么:要有这个4字节对齐:
结合之前栈帧的学习和上面代码的测试结果
怎么办到的:
第一步理解:4的倍数
既然是4的最小整数倍取整,那么本质是:x=4*m,m是具体几倍。对7来讲,m就是2,对齐的结果就是8
而m具体是多少,取决于n是多少
如果n能整除4,那么m就是n/4
如果n不能整除4,那么m就是n/4+1
上面是两种情况,如何合并成为一种写法呢?
常见做法是 ( n+sizeof(int)-1) )/sizeof(int) -> (n+4-1)/4
如果n能整除4,那么m就是(n+4-1)/4->(n+3)/4, +3的值无意义,会因取整自动消除,等价于 n/4
如果n不能整除4,那么n=最大能整除4部分+r,1<=r<4 那么m就是 (n+4-1)/4->(能整除4部分+r+3)/4,其中
4<=r+3<7 -> 能整除4部分/4 + (r+3)/4 -> n/4+1
第二步理解:最小4字节对齐数
搞清楚了满足条件最小是几倍问题,那么,计算一个最小数字x,满足 x>=n && x%4==0,就变成了
((n+sizeof(int)-1)/sizeof(int))[最小几倍] * sizeof(int)[单位大小] -> ((n+4-1)/4)*4
这样就能求出来4字节对齐的数据了,其实上面的写法,在功能上,已经和源代码中的宏等价了。
第三步理解:理解源代码中的宏
拿出简洁写法:((n+4-1)/4)* 4,设w=n+4-1, 那么表达式可以变化成为 (w/4)*4,而4就是2^2,w/4,不就相当于右移两位吗?,再次*4不就相当左移两位吗?先右移两位,在左移两位,最终结果就是,最后2个比特位被清空为0!
需要这么费劲吗?
w & ~3 不香吗?
所以,简洁版:(n+4-1) & ~(4-1)
原码版:( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) ),无需先/,再*

5 . 原理图

#include <stdio.h>
#include <windows.h>
//num: 表示传入参数的个数
int FindMax ( int num , ...)
        va_list arg ; // 定义可以访问可变参数部分的变量,其实是一个 char* 类型
        va_start ( arg , num ); // 使 arg 指向可变参数部分
        int max = va_arg ( arg , int ); // 根据类型,获取可变参数列表中的第一个数据
        for ( int i = 0 ; i < num - 1 ; i ++ ) // 获取并比较其他的
        int curr = va_arg ( arg , int );
        if ( max < curr )
        max = curr ;
        
        va_end ( arg ); //arg 使用完毕,收尾工作。本质就是讲 arg 指向 NULL
        return max ;
int main ()
        //为了方便查看,我们参数换成直观的参数
        int max = FindMax ( 5 , 0x11 , 0x22 , 0x33 , 0x44 , 0x55 );
        printf ( "max = %d\\n" , max );
        system ( "pause" );
        return 0 ;

 

今天的内容就到这里了哈!!!

要是认为作者有一点帮助你的话!

就来一个点赞加关注吧!!!当然订阅是更是求之不得!

最后的最后谢谢大家的观看!!!

你们的支持是作者写作的最大动力!!!

下期见哈!!!

 

以上是关于函数栈帧与可变参数列表 p1可变参数列表(完结)( C语言从入门到入土(进阶篇)的主要内容,如果未能解决你的问题,请参考以下文章

C语言 可变参数列表

可变参数列表

可变参数列表

未知类型的可变构造函数参数列表

透明地通过带有可变参数列表的函数

Scala可变参数列表,命名参数和参数缺省