gcc如何传递C/C++函数的聚合类参数

news/2025/11/5 19:48:21/文章来源:https://www.cnblogs.com/tsecer/p/19194491

gcc如何传递C/C++函数的聚合类参数

intro

通常C/C++中函数一般不会直接传递一个结构,更多处于性能考虑都会传递一个const类型的结构引用/指针。但是既然语言本身支持这种语法功能,编译器必定就要有对应的实现/规范/标准:否则不同编译器(例如gcc和llvm)生成的二进制文件(lib库)之间就没有办法兼容。特别是聚合类对象(struct/array)更加复杂,所以就更需要有清晰明确的标准。

ABI

以最常见的X86系统为例,函数参数传递相关内容在System V Application Binary Interface的3.2.3 Parameter Passing一节中。

其中相关信息如下所示,看起来很详细,但是也有些困惑。其中多次提到了8字节(eightbyte)。例如其中的

  1. If the size of the aggregate exceeds a single eightbyte, each is classified separately. Each eightbyte gets initialized to class NO_CLASS.

并且看起来是不考虑类型,只考虑内存大小。那么如果一个结构在pack之后跨越两个不同的8bytes会如何处理?如果是一个数组,并且数组类型是char类型会怎么处理?

Classification The size of each argument gets rounded up to eightbytes.
...
The classification of aggregate (structures and arrays) and union types works as follows:

  1. If the size of an object is larger than four eightbytes, or it contains unaligned fields, it has class MEMORY

  2. If a C++ object has either a non-trivial copy constructor or a non-trivial destructor 11, it is passed by invisible reference (the object is replaced in the parameter list by a pointer that has class INTEGER) .

  3. If the size of the aggregate exceeds a single eightbyte, each is classified separately. Each eightbyte gets initialized to class NO_CLASS.

  4. Each field of an object is classified recursively so that always two fields are considered. The resulting class is calculated according to the classes of the fields in the eightbyte:
    (a) If both classes are equal, this is the resulting class.
    (b) If one of the classes is NO_CLASS, the resulting class is the other
    class.
    (c) If one of the classes is MEMORY, the result is the MEMORY class.
    (d) If one of the classes is INTEGER, the result is the INTEGER.
    (e) If one of the classes is X87, X87UP, COMPLEX_X87 class, MEMORY is used as class.
    (f) Otherwise class SSE is used.

  5. Then a post merger cleanup is done:
    (a) If one of the classes is MEMORY, the whole argument is passed in memory.
    (b) If X87UP is not preceded by X87, the whole argument is passed in memory.
    (c) If the size of the aggregate exceeds two eightbytes and the first eightbyte isn’t SSE or any other eightbyte isn’t SSEUP, the whole argument is passed in memory.

主体代码

由于文档说明可能有些模糊,如果结合代码来看,文档的描述会清晰很多。

函数最开始就是以8字节为单位向上取整,获得eightbytes的数量(对应words变量)。的确是把每个word都初始化为X86_64_NO_CLASS。如果总大小大于64个字节就一定通过堆栈来传递参数。

这里的8bytes是单单从存储大小角度考虑的,根本原因是因为64bits系统中,一个寄存器最多只能存储8个字节。在这个时候,已经触碰到编译器的一个核心功能了:把程序中的类型转换为内存/寄存器

///@file: gcc\config\i386\i386.cc
/* Classify the argument of type TYPE and mode MODE.CLASSES will be filled by the register class used to pass each wordof the operand.  The number of words is returned.  In case the parametershould be passed in memory, 0 is returned. As a special case for zerosized containers, classes[0] will be NO_CLASS and 1 is returned.BIT_OFFSET is used internally for handling records and specifies offsetof the offset in bits modulo 512 to avoid overflow cases.See the x86-64 PS ABI for details.
*/static int
classify_argument (machine_mode mode, const_tree type,enum x86_64_reg_class classes[MAX_CLASSES], int bit_offset,int &zero_width_bitfields)
{HOST_WIDE_INT bytes= mode == BLKmode ? int_size_in_bytes (type) : (int) GET_MODE_SIZE (mode);int words = CEIL (bytes + (bit_offset % 64) / 8, UNITS_PER_WORD);/* Variable sized entities are always passed/returned in memory.  */if (bytes < 0)return 0;if (mode != VOIDmode){/* The value of "named" doesn't matter.  */function_arg_info arg (const_cast<tree> (type), mode, /*named=*/true);if (targetm.calls.must_pass_in_stack (arg))return 0;}if (type && (AGGREGATE_TYPE_P (type)|| (TREE_CODE (type) == BITINT_TYPE && words > 1))){int i;tree field;enum x86_64_reg_class subclasses[MAX_CLASSES];/* On x86-64 we pass structures larger than 64 bytes on the stack.  */if (bytes > 64)return 0;for (i = 0; i < words; i++)classes[i] = X86_64_NO_CLASS;/* Zero sized arrays or structures are NO_CLASS.  We return 0 tosignalize memory class, so handle it as special case.  */if (!words){classes[0] = X86_64_NO_CLASS;return 1;}

struct

结构是按照结构的每个filed来遍历的,在处理每个field的时候进行了递归调用classify_argument函数。将classify_argument计算成的class和所在struct进行合并(merge_classes (subclasses[i], classes[i + pos]))。

其中还有一个重要细节:只要任何一个字段不能放在寄存器中,整个结构都不能放在寄存器中:

		      if (!num)return 0;

完整代码如下:

static int
classify_argument (machine_mode mode, const_tree type,enum x86_64_reg_class classes[MAX_CLASSES], int bit_offset,int &zero_width_bitfields)
{
///.../* Classify each field of record and merge classes.  */switch (TREE_CODE (type)){case RECORD_TYPE:/* And now merge the fields of structure.  */for (field = TYPE_FIELDS (type); field; field = DECL_CHAIN (field)){if (TREE_CODE (field) == FIELD_DECL){int num;if (TREE_TYPE (field) == error_mark_node)continue;/* Bitfields are always classified as integer.  Handle themearly, since later code would consider them to bemisaligned integers.  */if (DECL_BIT_FIELD (field)){if (integer_zerop (DECL_SIZE (field))){if (DECL_FIELD_CXX_ZERO_WIDTH_BIT_FIELD (field))continue;if (zero_width_bitfields != 2){zero_width_bitfields = 1;continue;}}for (i = (int_bit_position (field)+ (bit_offset % 64)) / 8 / 8;i < ((int_bit_position (field) + (bit_offset % 64))+ tree_to_shwi (DECL_SIZE (field))+ 63) / 8 / 8; i++)classes[i]= merge_classes (X86_64_INTEGER_CLASS, classes[i]);}else{int pos;type = TREE_TYPE (field);/* Flexible array member is ignored.  */if (TYPE_MODE (type) == BLKmode&& TREE_CODE (type) == ARRAY_TYPE&& TYPE_SIZE (type) == NULL_TREE&& TYPE_DOMAIN (type) != NULL_TREE&& (TYPE_MAX_VALUE (TYPE_DOMAIN (type))== NULL_TREE)){static bool warned;if (!warned && warn_psabi){warned = true;inform (input_location,"the ABI of passing struct with"" a flexible array member has"" changed in GCC 4.4");}continue;}num = classify_argument (TYPE_MODE (type), type,subclasses,(int_bit_position (field)+ bit_offset) % 512,zero_width_bitfields);if (!num)return 0;pos = (int_bit_position (field)+ (bit_offset % 64)) / 8 / 8;for (i = 0; i < num && (i + pos) < words; i++)classes[i + pos]= merge_classes (subclasses[i], classes[i + pos]);}}}break;

array

数组的处理相对比较简单:对数组类型调用classify_argument函数,然后以每个类型的结构为pattern来对整个数组占用的内存循环施加pattern效果。如果数组类型可以放入到一个8bytes中,那么每个classes元素都是数组类型的class(subclasses)。

这里或许是假设了如果数组类型不大于8个bytes,那么整个类型的所有内存都是相同的存储class。

	    for (i = 0; i < words; i++)classes[i] = subclasses[i % num];

相对完整代码如下

static int
classify_argument (machine_mode mode, const_tree type,enum x86_64_reg_class classes[MAX_CLASSES], int bit_offset,int &zero_width_bitfields)
{
///...case ARRAY_TYPE:/* Arrays are handled as small records.  */{int num;num = classify_argument (TYPE_MODE (TREE_TYPE (type)),TREE_TYPE (type), subclasses, bit_offset,zero_width_bitfields);if (!num)return 0;/* The partial classes are now full classes.  */if (subclasses[0] == X86_64_SSESF_CLASS && bytes != 4)subclasses[0] = X86_64_SSE_CLASS;if (subclasses[0] == X86_64_SSEHF_CLASS && bytes != 2)subclasses[0] = X86_64_SSE_CLASS;if (subclasses[0] == X86_64_INTEGERSI_CLASS&& !((bit_offset % 64) == 0 && bytes == 4))subclasses[0] = X86_64_INTEGER_CLASS;for (i = 0; i < words; i++)classes[i] = subclasses[i % num];break;}

未对齐字段

没有自然对齐的字段不能通过寄存器传递(Misaligned fields are always returned in memory)。

static int
classify_argument (machine_mode mode, const_tree type,enum x86_64_reg_class classes[MAX_CLASSES], int bit_offset,int &zero_width_bitfields)
{
///.../* Compute alignment needed.  We align all types to natural boundaries withexception of XFmode that is aligned to 64bits.  */if (mode != VOIDmode && mode != BLKmode){int mode_alignment = GET_MODE_BITSIZE (mode);if (mode == XFmode)mode_alignment = 128;else if (mode == XCmode)mode_alignment = 256;if (COMPLEX_MODE_P (mode))mode_alignment /= 2;/* Misaligned fields are always returned in memory.  */if (bit_offset % mode_alignment)return 0;}

结构大于2个8bytes不能使用寄存器

非SSE类型,如果总长度超过两个8字节,一定通过内存传递。

      if (words > 2){/* When size > 16 bytes, if the first one isn'tX86_64_SSE_CLASS or any other ones aren'tX86_64_SSEUP_CLASS, everything should be passed inmemory.  */if (classes[0] != X86_64_SSE_CLASS)return 0;for (i = 1; i < words; i++)if (classes[i] != X86_64_SSEUP_CLASS)return 0;}

一些情况分类

  1. 结构/数组中有未对齐字段

代码中可以看到直接返回0,也就是只能放在堆栈中。

  1. 结构中多个字段在同一个8字节范围内

此时num为1,而多个field在classed数组中的下标可能都是相同的(pos值相同:classes每个元素对于结构的8个bytes,所以在同一个8字节空间内的filed就在同一个classes数组元素中)。

		      pos = (int_bit_position (field)+ (bit_offset % 64)) / 8 / 8;for (i = 0; i < num && (i + pos) < words; i++)classes[i + pos]= merge_classes (subclasses[i], classes[i + pos]);

test

结构B为两个8字节,作为函数参数时整个结构都通过寄存器传递;结构C在B的基础上加一个字节(因为超过了两个8字节),就需要通过堆栈传递。

tsecer@harry: cat func_struct_param.cpp
struct A
{int i;float f;
};struct B
{A aa[2];
};struct C
{B b;char c;
};int foo(B b)
{return b.aa[0].i + b.aa[1].f;
}int bar(C c)
{return c.c + c.b.aa[0].i;
}tsecer@harry: gcc -S func_struct_param.cpp
tsecer@harry: cat func_struct_param.s.file   "func_struct_param.cpp".text.globl  _Z3foo1B.type   _Z3foo1B, @function
_Z3foo1B:
.LFB0:.cfi_startprocendbr64pushq   %rbp.cfi_def_cfa_offset 16.cfi_offset 6, -16movq    %rsp, %rbp.cfi_def_cfa_register 6movq    %rdi, %raxmovq    %rsi, %rcxmovq    %rcx, %rdxmovq    %rax, -16(%rbp)movq    %rdx, -8(%rbp)movl    -16(%rbp), %eaxpxor    %xmm1, %xmm1cvtsi2ssl       %eax, %xmm1movss   -4(%rbp), %xmm0addss   %xmm1, %xmm0cvttss2sil      %xmm0, %eaxpopq    %rbp.cfi_def_cfa 7, 8ret.cfi_endproc
.LFE0:.size   _Z3foo1B, .-_Z3foo1B.globl  _Z3bar1C.type   _Z3bar1C, @function
_Z3bar1C:
.LFB1:.cfi_startprocendbr64pushq   %rbp.cfi_def_cfa_offset 16.cfi_offset 6, -16movq    %rsp, %rbp.cfi_def_cfa_register 6movzbl  32(%rbp), %eaxmovsbl  %al, %edxmovl    16(%rbp), %eaxaddl    %edx, %eaxpopq    %rbp.cfi_def_cfa 7, 8ret.cfi_endproc
.LFE1:.size   _Z3bar1C, .-_Z3bar1C.ident  "GCC: (Ubuntu 13.3.0-6ubuntu2~24.04) 13.3.0".section        .note.GNU-stack,"",@progbits.section        .note.gnu.property,"a".align 8.long   1f - 0f.long   4f - 1f.long   5
0:.string "GNU"
1:.align 8.long   0xc0000002.long   3f - 2f
2:.long   0x3
3:.align 8
4:
tsecer@harry:

outro

这里并没有详细分析流程的所有细节,只是想说明,在分析汇编代码的时候要注意:

C/C++的函数参数传递并不是结构/数组就一定通过堆栈传递;反过来说,通过(分散的)寄存器传递的函数参数也可能是(同一个)结构的成员。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/957094.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

31

所学时间:9小时 博客量:1 代码量:1百 所学知识:软件设计实验5:建造者模式 本次实验属于模仿型实验,通过本次实验学生将掌握以下内容: 1、理解建造者模式的动机,掌握该模式的结构; 2、能够利用建造者模式解决实…

deepseek-ocr部署

deepseek-ocr部署部署环境 24GB显存+Ubuntu22 依赖 torch==2.6.0 torchvision==0.21.0 torchaudio==2.6.0 --index-url https://download.pytorch.org/whl/cu118 transformers==4.46.3 tokenizers==0.20.3 einops addi…

TiDB数据库从零开始

TiDB数据库从零开始 学习地址:……/s/1yvdOI3R3yT-zL5b4xfQsRg 提取码:3w36 在数字化浪潮席卷全球的今天,数据已成为企业的核心资产。传统单机数据库在面对海量数据和高并发场景时逐渐力不从心,分布式数据库应运而…

锁定缓冲区的概念

锁定缓冲区的概念我们通常所说的Cache是高速缓存,它位于CPU和主内存之间,用于加速内存访问。但是,Cache的内容通常是易失的,而且其行为对软件来说是透明的(即由硬件自动管理)。然而,在某些体系结构中,比如龙芯…

【UEGamePlay】- 3C篇(一) : 输入

前言 本系列想基于技术策划的角度去入手整个文章,包含剖析UE原生的3C实现,以及在实际工程中,我们需要将哪些需求转换为能够实际落地的数据以便和设计蓝图对接,以及在前期如何快速出原型。 文章不会讨论所有3C的表现…

Ubuntu下安装Nvidia驱动CUDAgpu_burn的一些步骤注意事项错误修复

1.下载安装系统,本文以25.04为例 2.安装vim apt-get install vim 3.更换国内源 1)sudo vim /etc/apt/sources.list.d/ubuntu.sources 2)将原本内容注释,并将以下内容复制进去Types: deb URIs: https://mirrors.ali…

郑州西亚斯学院举办智能体创新大赛

这是郑州西亚斯学院智能体创新大赛的示例文件,如果你看到这个信息,说明这个文件的内容已经正常发送。

JavaWeb02-Maven

1.MavenMaven是专门用于管理和构建Java项目的工具,它的主要功能有:提供了一套标准化的项目结构 提供了一套标准化的构建流程(编译,测试,打包,发布......) 提供了一套依赖管理机制标准化的项目结构标准化的构建流…

NOIp模拟2 模拟退火 笔记

正好昨天学了模拟退火,就来写个笔记…… 模拟退火,顾名思义就是模拟退火(看不懂不用担心,模拟退火和退火关系不大……)。 啥意思? 借用哦哎wiki上的图: ; 其实就是随机改变当前方案,根据答案是否增加决定是否真…

课后作业(异常捕获)

java中的异常捕获语句 Try{ //可能发生运行错误的代码; } catch(异常类型 异常对象引用){ //用于处理异常的代码 } finally{ //用于“善后” 的代码 } 将可能发生异常的代码放进try语句中,程序检测到发生异常时…

UEFI 启动的各阶段介绍 - 阿源

UEFI启动的七个阶段介绍系统固件开发学习系列: 一、EDKII环境搭建 & QEMU虚拟机双平台安装 二、EDKII工程结构介绍目录 一、整体概念 二、SEC 安全初始化阶段 三、PEI 预 EFI 初始化阶段 四、DXE—驱动执行环境阶…

CSP 2025 游记总结

T1 大约开考 40 min 后做出,有点慢了。 反悔贪心,按照每个人最大的满意度与次大值的差为第一关键字,次大值与最小值的差为第二关键字从大到小排序,然后挨个取就行了。 期望得分:\(100\)点击查看代码 #include<…

在AI技术快速实现创意的时代,挖掘用户真实需求成为制胜关键——某知名macOS防睡眠工具需求洞察

本文基于某知名macOS防睡眠工具的文档和用户反馈分析,揭示了用户在使用过程中提出的核心需求,包括连接电源自动激活、外接显示器检测优化、图标显示改进等关键功能改进建议,为产品迭代提供重要参考。a.内容描述核心…

2025 年 11 月重型货架厂家推荐排行榜,模具/高位/阁楼/平台/仓储/冷库/定制/立体库/智能/窄巷道/钢平台/抽屉/悬臂/穿梭车/搬运机器人/天金冈货架公司精选

2025年11月重型货架厂家推荐排行榜:模具/高位/阁楼/平台/仓储/冷库/定制/立体库/智能/窄巷道/钢平台/抽屉/悬臂/穿梭车/搬运机器人/天金冈货架公司精选 一、行业背景与发展趋势 随着现代物流体系的不断完善和智能制造…

2025 年 11 月小规模财税合规服务商推荐榜:专业记账、税务申报与风险规避一站式解决方案

2025 年 11 月小规模财税合规服务商推荐榜:专业记账、税务申报与风险规避一站式解决方案 行业背景与发展趋势 随着数字经济时代的深入发展,小规模企业的财税合规需求呈现出专业化、精细化、智能化的新特征。在税收监…

易路全球AI峰会Day1收官,引领AI HR新未来

11月5日下午,由易路主办的「人机重塑,共建XIN生态」全球企业AI云端数字峰会暨2025企业AI HR创新应用案例颁奖盛典,震撼开讲。 作为人力资源领域的首场数字人云端峰会,本届峰会聚焦AI前沿视角与技术创新落地,为期3…

2025 年 11 月石墨坩埚加工设备,石墨电极与接头加工设备厂家推荐排行榜,专业实力与高效生产口碑之选

2025 年 11 月石墨坩埚加工设备,石墨电极与接头加工设备厂家推荐排行榜,专业实力与高效生产口碑之选 石墨材料加工设备作为现代工业制造的关键环节,在冶金、新能源、半导体等领域发挥着不可替代的作用。随着技术进步…

P1668 [USACO04DEC] Cleaning Shifts S 题解

P1668 [USACO04DEC] Cleaning Shifts S 题解P1668 [USACO04DEC] Cleaning Shifts S 题解 题目传送门 我的博客 前言 这道题有 \(3\) 种方法。本文将依次进行讲解。 做法 \(1\):贪心。笔者一开始的思路就是这个。 做法…