va_list和va_start和((A*)0)->a

本文详细解释了C语言中可变参数函数的工作原理,包括va_list、va_start、va_arg及va_end的使用方法。并通过示例展示了如何计算参数地址和大小,确保参数指针的有效性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

C语言函数是从右到左入栈的

va_list ap;//=char *ap;(一个字符指针)

va_start(ap,v) 中 ( ap = (va_list)_ADDRESSOF(v) + _INTSIZEOF(v) )//返回参数v后的参数列表地址(V地址+V的长度)

va_arg(ag,type):va_arg(ap,t)    ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) )//取指定类型的值int i=va_arg(ap,int),并且使ap指向向一个参数

va_end(ag):清空参数列表,并置参数指针ag无效。说明:指针ag被置无效后,可以通过调用 va_start()、va_arg恢复arg。每次调用va_start() / va_arg()后,必须得有相应的va_end()与之匹配。参数指针可以在参数列表中随意地来回移动,但必须在va_start() … va_end()之内

注:

其中:#define _INTSIZEOF(n) ( (sizeof(n) + sizeof(int) – 1) & ~(sizeof(int) – 1)

~是位取反的意思。_INTSIZEOF(n)整个做的事情就是将n的长度化为int长度的整数倍。

比如n为5,二进制就是101b,int长度为4,二进制为100b,那么n化为int长度的整数倍就应该为8。((5+4-1)/4)*4=8;

~(sizeof(int) – 1) )就应该为~(4-1)=~(00000011b)=11111100b,这样任何数& ~(sizeof(int) – 1) )后最后两位肯定为0,就肯定是4的整数倍了。

(sizeof(n) + sizeof(int) – 1)就是将大于4m但小于等于4(m+1)的数提高到大于等于4(m+1)但小于4(m+2),这样再& ~(sizeof(int) – 1) )后就正好将原长度补齐到4的倍数了 

即:

((5+4-1)/4)

(sizeof(n) + sizeof(int) – 1)&~(sizeof(int-1)

 

  栈底 高地址 
    | .......      
    | 函数返回地址 
    | .......       
    | 函数最后一个参数 
    | ....                        
    | 函数第一个可变参数       <--va_start后ap指向  
    | 函数最后一个固定参数 
    | 函数第一个固定参数  
    栈顶 低地址 

 Linux内核中,用两个非常巧妙地宏实现了,一个是offsetof宏,另一个是container_of宏,下面讲解一下这两个宏。

1.  offsetof宏
【定义】:#define offsetof(TYPE, MEMBER) ((size_t) & ((TYPE *)0)->MEMBER )
【功能】: 获得一个结构体变量成员在此结构体中的偏移量。
【例子】:
struct A
{
    int x ;
    int y;
    int z;
};
 
void main()
{
    printf("the offset of z is %d",offsetof( struct A, z )  );
}
 
// 输出结果为 8
【分析】:
 
该宏,TYPE为结构体类型,MEMBER 为结构体内的变量名。
 
(TYPE *)0) 是欺骗编译器说有一个指向结构TYPE 的指针,其地址值0
 
(TYPE *)0)->MEMBER 是要取得结构体TYPE中成员变量MEMBER的地址. 因为基址为0,所以,这时MEMBER的地址当然就是MEMBER在TYPE中的偏移了

转载于:https://www.cnblogs.com/fujinliang/archive/2012/11/24/2785348.html

生成开始于 19:06... 1>------ 已启动生成: 项目: LearnC, 配置: Debug x64 ------ 1>test.cpp 1>class _PMD size(12): 1> +--- 1> 0 | mdisp 1> 4 | pdisp 1> 8 | vdisp 1> +--- 1>class _TypeDescriptor size(16): 1> +--- 1> 0 | pVFTable 1> 8 | spare 1>16 | name 1> +--- 1>class _s__CatchableType size(36): 1> +--- 1> 0 | properties 1> 4 | pType 1>12 | _PMD thisDisplacement 1>24 | sizeOrOffset 1>28 | copyFunction 1> +--- 1>class _s__CatchableTypeArray size(4): 1> +--- 1> 0 | nCatchableTypes 1> 4 | arrayOfCatchableTypes 1> +--- 1>class _s__ThrowInfo size(28): 1> +--- 1> 0 | attributes 1> 4 | pmfnUnwind 1>12 | pForwardCompat 1>20 | pCatchableTypeArray 1> +--- 1>class _s__RTTIBaseClassDescriptor size(36): 1> +--- 1> 0 | pTypeDescriptor 1> 8 | numContainedBases 1>12 | _PMD where 1>24 | attributes 1>28 | pClassDescriptor 1> +--- 1>class _s__RTTIBaseClassArray size(4): 1> +--- 1> 0 | arrayOfBaseClassDescriptors 1> | <alignment member> (size=3) 1> +--- 1>class _s__RTTIClassHierarchyDescriptor size(20): 1> +--- 1> 0 | signature 1> 4 | attributes 1> 8 | numBaseClasses 1>12 | pBaseClassArray 1> +--- 1>class _s__RTTICompleteObjectLocator2 size(36): 1> +--- 1> 0 | signature 1> 4 | offset 1> 8 | cdOffset 1>12 | pTypeDescriptor 1>20 | pClassDescriptor 1>28 | pSelf 1> +--- 1>class __s_GUID size(16): 1> +--- 1> 0 | Data1 1> 4 | Data2 1> 6 | Data3 1> 8 | Data4 1> +--- 1>class __noop_coroutine_frame_standard size(24): 1> +--- 1> 0 | resume 1> 8 | destroy 1>16 | promise 1> | <alignment member> (size=7) 1> +--- 1>class __noop_coroutine_frame_legacy size(16): 1> +--- 1> 0 | resume 1> 8 | flag 1>12 | index 1> +--- 1>class __crt_locale_data_public size(16): 1> +--- 1> 0 | _locale_pctype 1> 8 | _locale_mb_cur_max 1>12 | _locale_lc_codepage 1> +--- 1>class __crt_locale_pointers size(16): 1> +--- 1> 0 | locinfo 1> 8 | mbcinfo 1> +--- 1>class _Mbstatet size(8): 1> +--- 1> 0 | _Wchar 1> 4 | _Byte 1> 6 | _State 1> +--- 1>class _iobuf size(8): 1> +--- 1> 0 | _Placeholder 1> +--- 1>class __vcrt_va_list_is_reference<struct __crt_locale_pointers * const> size(1): 1> +--- 1> +--- 1>class __vcrt_assert_va_start_is_not_reference<struct __crt_locale_pointers * const> size(1): 1> +--- 1> +--- 1>class __vcrt_va_list_is_reference<wchar_t const * const> size(1): 1> +--- 1> +--- 1>class __vcrt_assert_va_start_is_not_reference<wchar_t const * const> size(1): 1> +--- 1> +--- 1>class __vcrt_va_list_is_reference<wchar_t const *> size(1): 1> +--- 1> +--- 1>class __vcrt_assert_va_start_is_not_reference<wchar_t const *> size(1): 1> +--- 1> +--- 1>class __vcrt_va_list_is_reference<struct __crt_locale_pointers *> size(1): 1> +--- 1> +--- 1>class __vcrt_assert_va_start_is_not_reference<struct __crt_locale_pointers *> size(1): 1> +--- 1> +--- 1>class __vcrt_va_list_is_reference<char const * const> size(1): 1> +--- 1> +--- 1>class __vcrt_assert_va_start_is_not_reference<char const * const> size(1): 1> +--- 1> +--- 1>class __vcrt_va_list_is_reference<char const *> size(1): 1> +--- 1> +--- 1>class __vcrt_assert_va_start_is_not_reference<char const *> size(1): 1> +--- 1> +--- 1>class _div_t size(8): 1> +--- 1> 0 | quot 1> 4 | rem 1> +--- 1>class _ldiv_t size(8): 1> +--- 1> 0 | quot 1> 4 | rem 1> +--- 1>class _lldiv_t size(16): 1> +--- 1> 0 | quot 1> 8 | rem 1> +--- 1>class <unnamed-tag> size(10): 1> +--- 1> 0 | ld 1> +--- 1>class <unnamed-tag> size(8): 1> +--- 1> 0 | x 1> +--- 1>class <unnamed-tag> size(4): 1> +--- 1> 0 | f 1> +--- 1>class <unnamed-tag> size(8): 1> +--- 1> 0 | x 1> +--- 1>class <unnamed-tag> size(12): 1> +--- 1> 0 | ld12 1> +--- 1>class StructVar size(16): 1> +--- 1> 0 | a 1> 4 | b 1> | <alignment member> (size=3) 1> 8 | c 1> +--- 1>LearnC.vcxproj -> F:\CplusPro\LearnC\x64\Debug\LearnC.exe ========== 生成: 1 成功,0 失败0 最新,0 已跳过 ========== ========== 生成 于 19:06 完成,耗时 00.439 秒 ========== 我不会看
最新发布
08-06
<think>好的,用户发来的是一段Visual Studio 2022在编译C++项目时的输出信息,看起来是关于结构体内存布局的详细报告。用户最后提到“我不会看”,显然需要帮助理解这些输出的含义。 首先,我需要分析用户提供的生成日志。这些日志显示了很多内部结构体的大小成员偏移,比如_PMD、_TypeDescriptor等,这些通常与RTTI(运行时类型信息)异常处理相关。用户可能在调试或优化代码时启用了某种编译器选项,导致这些信息被输出。 接下来,用户的问题是关于如何理解这些输出。我需要解释这些结构体的作用,以及它们的大小成员布局的意义。可能还需要说明为什么这些信息会被显示出来,比如是否启用了特定的编译器标志,如/d1reportAllClassLayout,这会显示所有类的内存布局。 然后,我需要考虑用户的背景。用户可能正在学习C++的内存管理或结构体内存对齐,或者在调试程序时遇到了问题。他们可能不太清楚这些内部结构如何影响程序,或者如何利用这些信息进行优化。 此外,用户可能对结构体内存对齐填充的概念不熟悉,需要简要解释这些概念,并举例说明如何通过调整结构体成员顺序或使用编译器指令(如#pragma pack)来优化内存使用。 还要注意用户可能的深层需求:他们可能希望减少内存占用、理解性能问题,或者确保跨平台兼容性。因此,在回答中需要涵盖这些方面,并提供相关的建议解决方案。 最后,确保回答结构清晰,分点说明,使用用户提供的例子进行解释,并给出进一步的问题以帮助用户深入理解相关主题。</think>### **解析 Visual Studio 2022 生成输出中的结构体内存布局** 你提供的编译日志中包含了大量与 C++ 运行时机制相关的内部结构体信息(如 RTTI、异常处理、协程等),以及自定义结构体 `StructVar` 的内存布局。以下是关键信息的解读: --- ### **1. 内部结构体的作用与意义** - **RTTI(运行时类型信息)相关结构**- `_TypeDescriptor`:描述类型的基本信息(如类型名、虚函数表指针)。 - `_s__RTTIBaseClassDescriptor`:描述基类信息(如偏移量、属性)。 - `_s__RTTICompleteObjectLocator2`:定位完整的对象类型信息。 - **异常处理相关结构**- `_s__ThrowInfo`:描述抛出异常的类型清理函数。 - `_s__CatchableType`:描述可捕获的异常类型。 - **其他运行时支持结构**- `_PMD`(Pointer to Member Data):用于指向类成员的指针。 - `__noop_coroutine_frame_standard`:协程框架的占位结构。 - `_iobuf`:标准 I/O 缓冲区的内部表示。 这些结构体是编译器为实现 C++ 特性(如多态、异常、RTTI)自动生成的辅助数据,开发者通常无需直接操作。 --- ### **2. 自定义结构体 `StructVar` 的内存布局** ```cpp class StructVar size(16): +--- 0 | a 4 | b | <alignment member> (size=3) 8 | c +--- ``` - **成员解释**- `a`:位于偏移 0,假设类型为 `int`(4 字节)。 - `b`:位于偏移 4,假设类型为 `char`(1 字节)。 - **填充字节**:`<alignment member>` 表示编译器插入了 3 字节填充(padding),以满足 `c` 的对齐要求。 - `c`:位于偏移 8,假设类型为 `double`(8 字节),需 8 字节对齐。 - **总大小**:16 字节(`a(4) + b(1) + padding(3) + c(8)`)。 --- ### **3. 为什么会出现这些信息?** - **编译器选项 `/d1reportAllClassLayout`**: 此选项强制编译器输出所有类/结构体的内存布局,包括编译器内部使用的结构体。通常用于调试内存对齐问题或优化结构体设计。 --- ### **4. 如何理解内存对齐?** - **对齐规则**- 每个成员的地址必须是其类型大小的整数倍(或编译器指定的对齐值,取较小者)。 - 结构体总大小必须是其最大成员对齐值的整数倍。 - **示例解析**- `StructVar` 中,`c` 是 `double`(8 字节),需 8 字节对齐。 - `b` 是 `char`(1 字节),之后需填充 3 字节,使 `c` 的起始地址为 8(即 `4 + 1 + 3 = 8`)。 --- ### **5. 如何优化结构体内存?** - **按成员大小从大到小排序**: ```cpp struct OptimizedStruct { double c; // 8 字节 int a; // 4 字节 char b; // 1 字节 }; // 总大小:16 字节(c(8) + a(4) + b(1) + padding(3)) ``` - 优化后填充减少,但可能仍需对齐。 - **使用 `#pragma pack` 强制紧凑对齐**: ```cpp #pragma pack(push, 1) struct PackedStruct { char c; int a; double d; }; #pragma pack(pop) // 总大小:13 字节(无填充,但可能降低性能) ``` --- ### **6. 常见问题与解决** - **问题 1:结构体大小远大于成员总** - **原因**:对齐填充导致内存浪费。 - **解决**:调整成员顺序或使用 `#pragma pack`。 - **问题 2:跨平台结构体布局不一致** - **原因**:不同编译器默认对齐规则不同(如 MSVC 默认按 8 字节对齐,GCC 默认按 8 字节但动态调整)。 - **解决**:显式指定对齐值(如 `#pragma pack(4)`)。 - **问题 3:未对齐访问导致性能下降** - **原因**:某些 CPU 架构(如 ARM)对未对齐访问有性能惩罚。 - **解决**:避免手动紧凑对齐,优先保证自然对齐。 --- ### **相关问题** 1. 如何通过代码验证结构体的实际大小对齐值? 2. `#pragma pack` `alignas` 的区别是什么? 3. 为什么 MSVC GCC 的默认对齐规则不同? 4. 如何通过 Visual Studio 的内存窗口查看结构体的二进制布局? 5. 未对齐的内存访问在 x86 ARM 架构上的影响有何不同?
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值