谈点近段对C语言结构体成员字节对齐问题的认识

缘由

这一段好几次纠缠于结构体成员字节对齐问题,经过一段时间的积累,慢慢就有了想说的东西

结论先行

  • 在更多的场景下,应该按照编译器默认字节对齐,对结构体进行编码
  • 在少量需要协议对齐、抑或外部接口要求的场景,才应该使用某种长度的字节对齐
  • 对于内存敏感的场景,首先应该考虑如何规避编译器默认字节对齐带来的膨胀影响,例如,调整结构体成员顺序,同时需要兼顾频繁访问内存的缓存Cache lined要求,以及成员语义内聚问题
  • 最后,在极少数情况下,使用一字节对齐。一字节对齐在非精细化研发的情况下,容易出现意想不到的字节不对齐问题,造成内存访问低效率情况

证据链

  • Linux内核中代码大量结构体,也未启用强制字节对齐

编译器默认字节对齐,引发的内存膨胀问题,不应该首先考虑的范围内,它是优化的一部分,应避免过早优化

动态申请内存字节对齐快速计算方法

在常用操作上,存在四字节对齐、八字节对齐等场景。这些场景,因为对齐长度均是2的幂次,存在较为快速的计算算法,也很直观。

#define ROUND_UP4(n) (((n) +  3) & ~3)
#define ROUND_UP8(n) (((n) +  7) & ~7)

//用void*指针来确定机器字长
#define ROUND_UP_MACHINE() (((n) + (sizeof(void*) - 1)) & ~(sizeof(void*) - 1))

对齐其它2的幂次的长度对齐,可以类比扩展

不同字节对齐结构体混用问题

对于在动态申请内存上,通过,强制转换指针,堆叠两个以上的结构体进行操作,特别是,存在与非编译器默认字节对齐的结构体换用情况,需要特别注意每个结构体堆放的起始位置,才可以不诱发字节不对齐引起的问题!

尽量不混用字节对齐不一致的结构体在同一份动态申请内存块中


试验程序

测试代码中,printf访问具有类似的成员u64的结构体,虽然在汇编代码层面上,两个打印过程除了必要的参数变化,其它汇编代码都一样, 但是执行消耗时间,对于字节不对齐的结构体大量读写访问,最终消耗时间会大一些!

见测试代码

#include <stdlib.h>
#include <stdio.h>
#include <stdint.h>
#include <sys/time.h>

typedef struct A {
    uint8_t u8;
    uint64_t u64;
} A;

/*Packed 编译指令*/
#pragma pack(push, 1)

typedef struct B {
    uint8_t u8;
    uint64_t u64;
} B;

#pragma pack(pop)

typedef struct C {
    uint8_t u8;
    uint64_t u64;
} C;

double getDiff(const struct timeval *start, const struct timeval *end);
double getDiff(const struct timeval *start, const struct timeval *end)
{
    #define USEC_UNIT (1000000)
    unsigned long diff = (end->tv_sec - start->tv_sec) * USEC_UNIT + (end->tv_usec - start->tv_usec);
    return (double)diff/USEC_UNIT;
}


int main(int argc, char **argv)
{
  struct timeval start, end;
  double smalldiff, bigdiff;
  printf("Sizeof(A): %2zu in default\nSizeof(B): %2zu with pack(1)\nSizeof(C): %2zu afer pack setting switch\n", sizeof(A), sizeof(B), sizeof(C));

  A a = {};
  B b = {};
  
  printf("\nThe asm code framewrok is the same, but the consumed time is different.\n");
  printf("Access A member: %lu to see its asm code\n", a.u64);
  printf("Access B member: %lu to see its asm code\n", b.u64);
  
  const unsigned int count = 100000000;
  printf("\nAccess struct member in default pack setting\n");
  
  unsigned int i = count;
  gettimeofday(&start, NULL);
  while(i--)
  {
      a.u64 += a.u8;
      ++a.u8;
  }
  gettimeofday(&end, NULL);
  smalldiff = getDiff(&start, &end);
  printf("End time: %ld.%ld, diff: %6lf in pack(default) setting\n", end.tv_sec, end.tv_usec, getDiff(&start, &end));
  
  i = count;
  gettimeofday(&start, NULL);
  while(i--)
  {
      b.u64 += b.u8;
      ++b.u8;
  }
  gettimeofday(&end, NULL);
  bigdiff = getDiff(&start, &end);
  printf("End time: %ld.%ld, diff: %6lf in pack(1) setting\n", end.tv_sec, end.tv_usec, getDiff(&start, &end));
  printf("Diff Percent: %lf%%\n", (bigdiff - smalldiff)*100/bigdiff);
  
  return 0;
}

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
C语言中,结构体字节对齐是为了优化内存的使用和提高程序的执行效率。字节对齐是指结构体成员在内存中的存放位置需要按照一定的规则进行排列。这样可以减少内存碎片并提高内存的访问效率。 通常情况下,结构体成员会按照其自身的字节大小进行对齐。例如,一个结构体成员的大小为2字节,那么它在内存中的存放位置就会从2的倍数地址开始。 此外,结构体的对齐方式还可以通过编译器的指令或者编译选项进行设置。常见的对齐方式有默认对齐方式和指定对齐方式。默认对齐方式是根据结构体成员的字节大小进行对齐,而指定对齐方式可以通过特定的语法来指定结构体的对齐方式。 总结起来,C语言结构体字节对齐的原则是,结构体成员在内存中的存放位置需要与所有成员中占内存最多的数据类型所占内存空间的字节数对齐。这样可以保证结构体的每个成员都能够被正确地访问,并且减少内存的浪费。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* *3* [C语言结构体字节对齐](https://blog.csdn.net/qq_41004932/article/details/113756628)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 100%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值