《C语言深度剖析》第六章 函数栈帧与可变参数列表 p1可变参数列表(完结)( C语言从入门到入土(进阶篇)_可变参数 四字节对齐(1)

img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上C C++开发知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

如果你需要这些资料,可以戳这里获取

++
){
//
获取并比较其他的

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
;

}

img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上C C++开发知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

如果你需要这些资料,可以戳这里获取

/

获取并比较其他的

int
curr

va_arg
(
arg
,
int
);

if
(
max
<
curr
){

max

curr
;

}

[外链图片转存中…(img-kD7mRURa-1715846581152)]
[外链图片转存中…(img-BEGfTa0Q-1715846581152)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上C C++开发知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

如果你需要这些资料,可以戳这里获取

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值