前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >【C】alignment

【C】alignment

作者头像
零式的天空
发布于 2022-03-02 08:52:27
发布于 2022-03-02 08:52:27
38300
代码可运行
举报
文章被收录于专栏:零域Blog零域Blog
运行总次数:0
代码可运行

内存访问粒度

如果没有深入的了解内存方面的东西, 我们可能会认为内存不过是简单的字节数组, 例如下面的形式 但是实际上, 计算机的处理器并不是以单个字节块为单位读写内存, 而是以2个,4个,8个,甚至16或者32个字节块为单位读写内存,如下图所示 我们将处理器访问内存单元的大小叫做其内存访问的粒度. 知道上面这一点很重要, 这也是C语言alignment的基础.

alignment 基本知识

为了说明对齐的基本原则, 下面举一个例子: 该示例很简单首先我们从地址0读取4个字节到处理器的register(寄存器), 然后我们从地址1读取4个字节到同一个寄存器.

首先我们看一下当处理器的访问粒度为1时的情况: 在这种情况下, 从地址0开始读和从地址1开始需要访问四次内存其所需的时间是相同的. 下面再看一下当处理器的内存访问粒度为2时的情况: 在这种情况下, 每次访问内存可以读取两个字节, 所以当从地址0 开始读时, 只需要访问两次内存, 这比单字节粒度减少了一半的时间. 但是当从地址1开始读时, 却需要访问三次内存, 第一次读[0,1]两个位置的字节, 第二次读[2,3]两个位置的字节,第三次读[4,5]两个位置的字节. 之所以出现这种情况是因为开始读取的位置(即1)没有位于处理器内存访问的边界上(当粒度为2时, 边界为0, 2, … , 2n), 所以需要额外的操作. 而这种地址就是所说的未对齐的地址(unaligned address). 最后我们再来看一下当内存访问粒度为4时的情况 当从地址0开始读时, 只需要一次内存访问, 而从地址1(未对齐的地址)开始读时需要两次内存访问. 通过上面的示例我们可以看到从未对齐位置访存要比从对齐位置访问多一次访存的操作, 然而除了多了一次访问之外, 我们还要注意到未对齐访存时会取到一些多余的数据, 处理器还要将这些多余的数据去除, 如下图所示: 从上面可以看到当读取了第一个内存块之后需要移除地址0的字节, 当读取了第二个内存块后要移除地址6-8的字节, 这很大程度上增加了处理器的负担.

内存对齐(memory alignment)

大多数CPU都要求位于内存中的变量和对象有一个特殊的起始位置(或者偏移 offset), 例如32位的处理器要求一个4字节整型在内存中的地址(第一个字节的地址)能被4整除, 我们就可以将这种要求(requirement)称为”memory alignment”. 当向内存中存入一个变量(variable)时, 此数据的地址应该是该数据alignmengt的整数倍.

基本类型对齐

对于基本类型来说, 它的alignment值和其所占的长度有关, 一般来说, 其alignment值就是其所占的字节数.

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
#include <stdio.h>

int main() {
    char c;
    short s;
    int i;
    float f;
    long long l;
    double d;

    printf("the size of c is %ld and  address of c is %p\n", sizeof(c), &c);
    printf("the size of s is %ld and  address of s is %p\n", sizeof(s), &s);
    printf("the size of i is %ld and  address of i is %p\n", sizeof(i), &i);
    printf("the size of f is %ld and  address of f is %p\n", sizeof(f), &f);
    printf("the size of l is %ld and  address of l is %p\n", sizeof(l), &l);
    printf("the size of d is %ld and  address of d is %p\n", sizeof(d), &d);
}

下面是基本的对齐原则:

在不同的机器上对其原则是不同的, 所以要根据具体的机器来, 上面所说的地址的最低一位为0, 就是起始位置必须是2的倍数, 最低两位为0, 则是起始位置必须是4的倍数.

结构体对齐

默认对齐方式

基本对齐原则如下:

  • 结构体对齐值: 其成员中自身对齐值最大的那个值
  • 结构体中成员的对齐值: 成员自身对齐值和结构体对齐值中较小的那个
  • 结构体的大小为结构体对齐值的整倍数
  • 结构体一般会通过插入空位的(padding)方式来满足上面的原则

比如下面的结构体:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
struct mystruct {
    char c;
    int i;
    short s;
};

在这个结构体中, c占1个字节, i占4个字节, s占两个字节, 所以mystruct的alignment值是4, 此时该结构体占12个字节, 下面是示意图

我们将上面的结构修改一下, 将s和i的顺序换一下

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
struct mystruct{
    char c;
    short s;
    int i;
};

那么此时该结构体所占的字节为8, 下面是内存示意图

所以为了尽量减少结构体中的空位, 我们应该合理的安排结构体中成员的顺寻.

如果结构体中包含结构体, 比如下面的代码:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
struct mystruct1{
    char c;
    double d;
};

struct mystruct2{
    char c;
    struct mystruct1 st1;
};

在这种情况下, 我们其实可以将mystruct1当成一个对齐值为8, 长度为16的基本类型处理, 只不过对齐值和所占的字节数是不固定的.

指定对齐值的大小

方法一: #pragma pack(value)

我们可以通过使用#pragma pack(value) 来指定对齐值的大小. 看下面的代码:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
#pragma pack(1) // 设置对齐值为1
struct mystruct {
    char c;
    int i;
    short s;
};
#pragma pack() // 取消指定对齐, 采用默认对齐方式

#pragma pack(1)将结构体及其成员的对齐值设为1, 也就是说结构体中的成员可以从任意的地址位置开始, 此时mystruct的大小为7.

注意如果pack中的value大于结构体原先的对齐值, 那么结构体仍然采用原先的对应值. 而结构体中每个数据成员的对齐,

按照#pragma pack指定的数值和这个数据成员自身长度中,比较小的那个进行.

方法二:__attribute__((aligned(n)))
  • __attribute__((aligned(n))) : 让所作用的结构成员对齐在n字节自然边界上。如果结构中有成员的长度大于n,则按照最大成员的长度来对齐。
  • __attribute__((packed)):取消结构在编译过程中的优化对齐,按照实际占用字节数进行对齐。

// 对齐值为16 struct mystruct { char c; int i; }__attribute__((aligned(16))); // 对齐值为4(以i为对齐值) struct mystruct2 { char c; int i; }__attribute__((aligned(2))); // 按实际占用字节对齐(即1) struct mystruct3 { char c; int i; }__attribute__((packed));

华丽的分割线

  1. 实际的运行效率 其实现在的计算机已经能很好没有对齐的情况了, 所以我们在一般的机器上运行时, 即使没有对齐也不会有很大的效率问题,
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
#include <stdio.h>
#include <stdlib.h>
#include <sys/time.h>
#include <stdint.h>

#define GET_TIME(now) {
    struct timeval t;
    gettimeofday(&t, NULL);
    now = t.tv_sec + t.tv_usec/1000000.0;
}

struct Foo {
    char x;
    short y;
    int z;
    char x1;
    int z1;
    char x2;
    int z2;
    char x3;
    long z3;
    char x4;
    long z4;
};

struct Foo foo;

struct Bar {
    char x;
    short y;
    int z;
    char x1;
    int z1;
    char x2;
    int z2;
    char x3;
    long z3;
    char x4;
    long z4;
} __attribute__((packed));

struct Bar bar;

int main() {
    double start, end, start1, end1 ;
    double time1, time2;
    
    GET_TIME(start1);
    uint64_t i;
    for ( i =0; i < RUNS; i++) {
        foo.z = i;
        foo.z1 =i+ 1;
        foo.z2 = i + 2;
        foo.z3 = i + 3;
        foo.z4 = i + 4;
    }
    GET_TIME(end1);
    time1 = end1 - start1;
    
    GET_TIME(start);
    for( i =0; i < RUNS; i++) {
        bar.z = i;
        bar.z1 = i + 1;
        bar.z2 = i + 2;
        bar.z3 = i + 3;
        bar.z4 = i + 4;
    }
    GET_TIME(end);
    time2 = end - start;
    
    printf("the size of Foo is %dn", sizeof(foo));
    printf("the size of Bar is %dn", sizeof(bar));
    
    printf("the time of Foo is %fn", time1);
    printf("the time of Bar is %fn", time2);
    
}

使用gcc编译代码

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
gcc -DRUNS=400000000 -o time test_time.c

然后执行会发现两者运行速度几乎是相同的. 但是在一些老的机器或者协处理器上内存对齐对速度还是有一定影响的.

参考文章

Data alignment: Straighten up and fly right Alignment in C

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2015-11-28,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
剖析c语言结构体的高级用法(二)
昨天分享了结构体里面的一些常见用法(因为测试代码测试的有点晚,有些地方没有分享完。),今天我们来继续分享结构体里面的其他用法。
用户6280468
2022/03/21
5440
剖析c语言结构体的高级用法(二)
嵌入式笔试面试题目系列(二)
本系列将按类别对题目进行分类整理,重要的地方标上星星,这样有利于大家打下坚实的基础。
Jasonangel
2021/05/28
7570
【海贼王编程冒险 - C语言海上篇】自定义类型:结构体,枚举,联合怎样定义?如何使用?
这种类型定义的变量也包含一系列的成员,特征是这些成员共用同一块空间(所以联合也叫做共用体)。
枫叶丹
2024/06/04
1240
【海贼王编程冒险 - C语言海上篇】自定义类型:结构体,枚举,联合怎样定义?如何使用?
熬夜整理的万字C/C++总结(四),值得收藏
注意:定义结构体类型时不要直接给成员赋值,结构体只是一个类型,编译器还没有为其分配空间,只有根据其类型定义变量时,才分配空间,有空间后才能赋值。
C语言与CPP编程
2021/08/03
3520
C/C++ 学习笔记五(结构体、字符与字符串)
结构体 C语言中复杂的数据结构都需要使用结构体表示,在这里说一下结构体的使用要点。 结构体内存分布以及对齐问题 编译器在为结构体分配内存时,并不会分配和所有成员数据长度和恰好相等的内存空间,而是
Celebi
2017/08/23
2.3K0
C/C++ 学习笔记五(结构体、字符与字符串)
C语言之——__attribute__
__attribute__ ((packed)) 的作用就是告诉编译器取消结构在编译过程中的优化对齐,按照实际占用字节数进行对齐,是GCC特有的语法。这个功能是跟操作系统没关系,跟编译器有关 。
用户4645519
2020/09/07
1.1K0
C语言之结构体(进阶篇)
友友们 大家好我是你们的小王同学 今天给大家带来结构体的进阶篇 如果觉得小王同学写的不错的话 给个三连吧 (求收藏 求关注 求点赞)谢谢你们这么好看还关注我(狗头)
王同学要努力
2022/12/20
5060
C语言之结构体(进阶篇)
【C语言】结构体的大小是如何计算的?(结构体对齐)
接着我们在主函数内部创建一个结构体变量s。这时我们就可以使用sizeof运算符来计算这个结构体的大小了。如,直接使用sizeof操作符计算变量s的大小:
修修修也
2024/04/01
1.5K0
【C语言】结构体的大小是如何计算的?(结构体对齐)
解析编程中不可或缺的基础:深入了解结构体类型
在编程中,结构体是一种自定义的数据类型,它允许开发人员将不同类型的数据组合在一起,并为其定义相关属性和行为。结构体提供了一种灵活的方式来表示复杂的数据结构,使得程序设计更加模块化和可读性更高。
DevKevin
2024/03/19
1570
解析编程中不可或缺的基础:深入了解结构体类型
C语言进阶(十一) - 自定义数据类型
C语言中本身包含了许多数据类型,但并不能够总是满足需要。自定义类型允许使用者创造出特定的且适合需要的类型。本文主要介绍结构体、位段、枚举与联合。
怠惰的未禾
2023/04/27
5090
C语言进阶(十一) - 自定义数据类型
【C语言】结构体与共用体深入解析
在C语言中,结构体(struct)和共用体(union)都是用来存储不同类型数据的复合数据类型,它们在程序设计中具有重要的作用。
池央
2025/01/25
1800
【C语言】结构体与共用体深入解析
__attribute__机制介绍
__attribute__可以设置函数属性(Function Attribute)、变量属性(Variable Attribute)和类型属性(Type Attribute)
阳光岛主
2019/02/19
3K0
一文轻松理解内存对齐
元素是按照定义顺序一个一个放到内存中去的,但并不是紧密排列的。从结构体存储的首地址开始,每个元素放置到内存中时,它都会认为内存是按照自己的大小(通常它为4或8)来划分的,因此元素放置的位置一定会在自己宽度的整数倍上开始,这就是所谓的内存对齐。
C语言与CPP编程
2020/10/23
12.8K0
一文轻松理解内存对齐
【C语言】宏定义详解
在C语言中,宏定义是一种强大的预处理器功能,用于在编译之前对代码进行替换和条件编译。宏定义通过预处理器指令进行定义和使用,能够使代码更加灵活和可维护。本文将对C语言中的宏定义进行全面的讲解,包括各种相关的预处理器指令及其用法。
LuckiBit
2024/12/11
3940
【C语言】宏定义详解
【C语言】自定义类型:结构体深入解析(二)结构体内存对齐&&宏offsetof计算偏移量&&结构体传参
本小节,我们学习结构的内存对齐,理解其对齐规则,内存对齐包含结构体的计算,使用宏offsetof计算偏移量,为什么要存在内存对齐?最后了解结构体的传参文章干货满满!学习起来吧😃!
学习起来吧
2024/02/29
4310
【C语言】自定义类型:结构体深入解析(二)结构体内存对齐&&宏offsetof计算偏移量&&结构体传参
【C语言】自定义类型总结
不是这样的,对于结构体的大小存在着内存对齐问题,基于此,我们首先需要了解内存对齐的规则
平凡的人1
2022/11/15
3250
【C语言】自定义类型总结
【C语言】一篇速通结构体
如上述代码这是一个结构体指针变量说明结构体指针变量p指向(->)的是一个结构体类型变量地址也就是保存x的地址。
謓泽
2023/03/01
4520
【C语言】一篇速通结构体
轻松拿捏C语言——自定义类型之【结构体】
因为⼀个结构体中再包含⼀个同类型的结构体变量,这样结构体变量的大小就会⽆穷的⼤,是不合理的
用户11162265
2024/06/14
950
C++从入门到精通——类对象模型
类对象模型是一种编程概念,用于描述和实现面向对象编程(OOP)中的类和对象。在这个模型中,类定义了对象的结构和行为,包括数据成员(属性)和成员函数(方法)。对象是类的实例,具有类的所有属性和方法。类对象模型支持封装、继承和多态等OOP特性,使得代码更加模块化、可重用和易于维护。通过类对象模型,程序员可以创建复杂的软件系统,提高开发效率和代码质量。
鲜于言悠
2024/04/11
2690
C++从入门到精通——类对象模型
学习笔记-C/C++-结构体与sizeof,内存对齐的题目怎么做
一些平台对某些特定类型的数据只能从某些特定地址开始存取。比如有些架构的CPU在访问 一个没有进行对齐的变量的时候会发生错误,那么在这种架构下编程必须保证字节对齐.
陈黎栋
2020/02/18
1K0
推荐阅读
相关推荐
剖析c语言结构体的高级用法(二)
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档