结构体对齐的重要性

2023-05-16

  最近在工作中被结构体对齐问题坑了一天的时间,郁闷的不行不行,特别记录下来,以供大家参考。
   事情是这样的,因业务需要增加了一个结构体,里面用到了信号量,当时写完联调的时候只测试了windows平台,因为win32/linux代码几乎一样,就没测试linux平台,可后来linux平台居然出现了莫名其妙的问题,在调用sem_timedwait等待信号量的时候直接就返回了,还没报错。

好了,我先将简化后的代码整理如下:

#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <errno.h>
#include <string>
#if defined _WIN32 || defined _WIN64
#include <windows.h>
#else
#include <semaphore.h>
#endif

#define TEST_PACK

#define MY_PRINTF(arg)		printf("%s\t = %p, sizeof = %lu\n", #arg, &arg, sizeof(arg))

#ifdef TEST_PACK
#pragma pack(1)
#endif
	//发布直播
	struct obj_releaslive_meeting
	{
		obj_releaslive_meeting()
		{
#if defined _WIN32 || defined _WIN64
			sigStart = CreateEvent(NULL, false, NULL, NULL);
			sigStop = CreateEvent(NULL, false, NULL, NULL);
#else
			sem_init(&sigStart, 0, 0);
			sem_init(&sigStop, 0, 0);
#endif
			devNo = 0;
			meetingId = "";
			startErrCode = -1;
			stopErrCode = -1;
		}

		~obj_releaslive_meeting()
		{
#if defined _WIN32 || defined _WIN64
			if (sigStart)
			{
				CloseHandle(sigStart);
				sigStart = NULL;
			}
			if (sigStop)
			{
				CloseHandle(sigStop);
				sigStop = NULL;
			}
#else
			sem_destroy(&sigStart);
			sem_destroy(&sigStop);
#endif			
		}

		uint16_t devNo;			
		std::string meetingId;
#if defined _WIN32 || defined _WIN64
		HANDLE sigStart;
		HANDLE sigStop;
#else
		sem_t sigStart;
		sem_t sigStop;
#endif
		
		int startErrCode;
		int stopErrCode;
	};
#ifdef TEST_PACK
#pragma pack()
#endif

int main()
{
	struct obj_releaslive_meeting *ptr = new obj_releaslive_meeting();

	MY_PRINTF(ptr->devNo);
	MY_PRINTF(ptr->meetingId);
	MY_PRINTF(ptr->sigStart);
	MY_PRINTF(ptr->sigStop);
	MY_PRINTF(ptr->startErrCode);
	MY_PRINTF(ptr->stopErrCode);

#if defined _WIN32 || defined _WIN64
	int ret = WaitForSingleObject(ptr->sigStart, 5000);
#else
	int ret = sem_wait(&ptr->sigStart);
#endif
	printf("ret = %d, error = %s\n", ret, strerror(errno));

	return 0;
}

然后我们分别看一下win32和linux平台的情况,简单说明一下,代码里使用TEST_PACK宏控制是结构体默认对齐还是按1字节对齐。

当时找了好久才发现,是别人的一个头文件里只写了#pragma pack(1),后面却没写#pragma pack(),然后他的头文件在某个地方又出现在我定义结构体的头文件前面了,所以影响到了我的结构体的对齐方式。

1.默认结构体对齐方式

1.1 Linux平台(x86-64, gcc version 7.4.0)

编译后执行,结果如预期一样可以被阻塞住:

0 14:49:07 ~/test $ ./a.out
ptr->devNo		 	  = 0x7fffd02cbe70, sizeof = 2
ptr->meetingId	      = 0x7fffd02cbe78, sizeof = 32
ptr->sigStart	      = 0x7fffd02cbe98, sizeof = 32
ptr->sigStop	      = 0x7fffd02cbeb8, sizeof = 32
ptr->startErrCode     = 0x7fffd02cbed8, sizeof = 4
ptr->stopErrCode	  = 0x7fffd02cbedc, sizeof = 4
^C

1.2 Win32平台(x86, vs2013)

编译的时候,需要在工程配置 -> C/C++ -> 代码生成 -> 安全检查 选项中设置禁用安全检查功能。结果同样正确:

ptr->devNo            = 00C74A38, sizeof = 2
ptr->meetingId        = 00C74A3C, sizeof = 28
ptr->sigStart         = 00C74A58, sizeof = 4
ptr->sigStop          = 00C74A5C, sizeof = 4
ptr->startErrCode     = 00C74A60, sizeof = 4
ptr->stopErrCode      = 00C74A64, sizeof = 4
ret = 258, error = No error

2.按1字节对齐方式

2.1 Linux平台

在gcc version 7.4.0(ubuntu1~18.04)下,程序收到来自系统的SIGABRT信号,异常退出:

0 15:14:15 ~/test $ ./a.out 
ptr->devNo		      = 0x7fffda944e70, sizeof = 2
ptr->meetingId	      = 0x7fffda944e72, sizeof = 32
ptr->sigStart	      = 0x7fffda944e92, sizeof = 32
ptr->sigStop	      = 0x7fffda944eb2, sizeof = 32
ptr->startErrCode     = 0x7fffda944ed2, sizeof = 4
ptr->stopErrCode      = 0x7fffda944ed6, sizeof = 4
The futex facility returned an unexpected error code.Aborted (core dumped)

在gcc version 4.8.5 20150623 (Red Hat 4.8.5-36)下,程序在调用sem_wait时候失败返回-1,提示无效的参数:

[root@localhost /]# ./test3 
ptr->devNo		      = 0x23c8010, sizeof = 2
ptr->meetingId	      = 0x23c8012, sizeof = 8
ptr->sigStart	      = 0x23c801a, sizeof = 32
ptr->sigStop	      = 0x23c803a, sizeof = 32
ptr->startErrCode     = 0x23c805a, sizeof = 4
ptr->stopErrCode      = 0x23c805e, sizeof = 4
ret = -1, error = Invalid argument

  1. 在Linux系统异常的真实原因其实是信号量地址未对齐,可以对比下自然对齐的sigStart地址0x7fffd02cbe98和强制1字节对齐的0x7fffda944e92。
  2. 在gcc version 4.8.5版本下,实际工程的现象是调用sem_wait直接返回,并且无任何错误。
  3. 另外,可以看到这两个平台的string实现机制不一样,在gcc 7.4.0版本占用32个字节,而在gcc 4.8.5版本只占用了一个同机器字长的指针大小。

2.2 Win32平台(x86, vs2013)

32位结果正常:

ptr->devNo            = 00164A38, sizeof = 2
ptr->meetingId        = 00164A3A, sizeof = 28
ptr->sigStart         = 00164A56, sizeof = 4
ptr->sigStop          = 00164A5A, sizeof = 4
ptr->startErrCode     = 00164A5E, sizeof = 4
ptr->stopErrCode      = 00164A62, sizeof = 4
ret = 258, error = No error

64位结果同样正常:

ptr->devNo            = 0000019FF0101CC0, sizeof = 2
ptr->meetingId        = 0000019FF0101CC2, sizeof = 40
ptr->sigStart         = 0000019FF0101CEA, sizeof = 8
ptr->sigStop          = 0000019FF0101CF2, sizeof = 8
ptr->startErrCode     = 0000019FF0101CFA, sizeof = 4
ptr->stopErrCode      = 0000019FF0101CFE, sizeof = 4
ret = 258, error = No error

总结

先看一下结构体为什么要对齐:

结构体对齐的原因:
  现代计算机中内存空间都是按照byte划分的,从理论上讲似乎对任何类型的变量的访问可以从任何地址开始,但实际情况是在访问特定变量的时候经常在特定的内存地址访问,这就需要各类型数据按照一定的规则在空间上排列,而不是顺序的一个接一个的排放,这就是对齐。
  各个硬件平台对存储空间的处理上有很大的不同。一些平台对某些特定类型的数据只能从某些特定地址开始存取。其他平台可能没有这种情况,但是最常见的是如果不按照适合其平台要求对数据存放进行对齐,会在存取效率上带来损失。比如有些平台每次读都是从偶地址开始,如果一个int型(假设为32位系统)如果存放在偶地址开始的地方,那么一个读周期就可以读出,而如果存放在奇地址开始的地方,就可能会需要2个读周期,并对两次读出的结果的高低字节进行拼凑才能得到该int数据。显然在读取效率上下降很多。这也是空间和时间的博弈

  由此说明,不同平台对于结构体不对齐的情况,处理方式是不一样的。
  对于Linux平台一个信号量占用32个字节,如果出现不对齐的情况,系统可能出于操作效率或者安全的考虑,直接就不支持/不允许这种操作,所以结果肯定不是正确的,但对于不同内核版本系统做出的行为也不太一样,如果像第一种情况直接收到SIGABRT还好,能够及时发现问题并改正,但如果是第二种情况并且sem_wait没有报错,这就给问题排查带来了很大的困难。
  对于Win32平台虽然运行结果正确,但我认为这并不能说明其就能正确的处理结构体不对齐的情况,因为Win32的信号量只有4/8个字节,不足够长,还不能完全说明问题。

PS:最后再说一遍#pragma pack(1),#pragma pack()预处理宏一定要成对使用,否则莫名其妙的影响了别人,真的很不好发现。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

结构体对齐的重要性 的相关文章

  • 如何做一个软件项目经理? ----写给公司所有的开发人员

    第一部分 xff1a 软件项目经理的要求 首先是一个管理者 xff0c 其次熟悉某些工具 xff0c 某几种语言 xff0c 行业背景 xff0c 项目管理技能 软件项目经理面临的恶劣环境 xff0c 我们绝大部分软件企业运行在相对混乱的状
  • vector介绍和基本使用

    文章目录 一 vector介绍二 vector使用 1 constructor 2 iterator 3 capacity 4 Element access 5 Modifiers 三 vector迭代器失效问题 一 vector介绍 ve
  • PELCO(派尔高)协议解析及下载(转载)

    PELCO xff08 派尔高 xff09 协议解析及下载 沈雪瑜 在IBMS接口开发 中 xff0c 我们需要用到一些常用的协议 xff0c 而PELCO 派尔高 的监控器材在我国有很广泛的应用 PELCO有自己的传输控制协议 xff0c
  • STM32&nbsp;HAL库&nbsp;STM3…

    原文地址 xff1a STM32 HAL库 STM32CUBEMX KEIL TIM1 PWM 四路输出可调 一 作者 xff1a 用户2797410335 硬件 xff1a TM32F407VET6 8M晶振 xff0c JLINK JT
  • 海康设备网络SDK开发NET_DVR_GetDeviceConfig

    由于官方的例子中没有关于NET DVR GetDeviceConfig的示例 xff0c 在此记录一下 NET DVR GET FIELD DETECTION 获取区域入侵侦测配置 xff0c 避免其他小伙伴踩坑 这里只记录主要代码 xff
  • geoserver热图

    1 参考 GeoServer发布Heatmap wenglabs 博客园 Rendering Transformations GeoServer 2 21 x User Manual 2 下载 GeoServer 及wps插件 xff0c
  • arcgis的lyr样式转qgis的sld样式

    需求 xff1a arcgis样式lyr要发布到geoserver 先说最终技术路线 xff1a qgis安装slyr插件 xff0c lyr转成xml xff0c 再通过xml配图后导出sld 避免的操作 xff1a lyr直接导出sld
  • httpclient海康ISAPI透传

    可以使用海康SDK调用NET DVR STDXMLConfig进行透传 xff0c 但是这种方式仍然比较麻烦 SDK的透传其实就是http的包装 xff0c 可以完全撇开海康SDK xff0c 也就是通过http的方式获取或者设置 xff0
  • openlayers文字随线的方向

    在不使用ol ext时需要自己计算方向 效果 xff1a 核心代码 xff1a function styleArrow start end title var arrowLonLat 61 end 0 43 start 0 2 end 1
  • openlayer点沿线动画

    Marker Animation 核心代码 xff1a 开始动画 let lastTime 61 Date now let distance 61 0 function moveFeature event const speed 61 10
  • ol-ext沿线动画

    参考示例 ol ext Openlayers feature animation 核心代码 xff1a 核心代码 var anim controler function animateFeature if routeFeature anim
  • test

    package org my cameratest import java io File import java io FileOutputStream import java io IOException import org kobj
  • C#操作Excel做Chart并输出成图

    lt summary gt 创建Chart xff0c 并设置相关属性 最后按照固定路径输出成gif图 lt summary gt lt param name 61 34 saveDocPath 34 gt 保存图片路径 lt param
  • Arcgis分级时出现Too many unique values (> 65536).

    只需要把默认值改的更大一点就行了 解决方法 xff1a 在默认值后面多添加几个0 xff0c 嘿嘿 xff0c 这样就行了
  • LCD与LED液晶显示屏的区别

    什么是LCD LCD是液晶显示屏Liquid Crystal Display的全称 xff0c 主要有TFT UFB TFD STN等几种类型的液晶显示屏无法定位程序输入点于动态链接库上 笔记本液晶屏常用的是TFT TFT xff08 Th
  • arcgis中连接excel时出错

    在arcmap中直接右键jion就可以关联excel xff0c 但是这次想使用小工具组合成ModelBuilder xff0c 所以先使用Copy Rows工具 xff0c 但是总是出错 xff0c 结果是一个字符引起的错误 箭头所指的方
  • 招行闪电贷“您的额度已被暂停”

    打电话给招行 xff0c 招行解释说名下两笔贷款都违规了 一笔是借贷还旧贷 xff0c 一笔是转账记录的备注写了还某某某首付 需要专款专用 xff0c 的确都违规了 招行也说只能先还完当前所有贷款 xff0c 才能尝试恢复额度 xff0c
  • su 鉴定故障解决办法(转)

    su su root命令输入密码后出现鉴定故障错误 这是因为在安装linux系统时没有给root用户设置密码 xff0c 重新设置密码即可 1 设置root密码 sudo passwd root gt 如果没有登录密码 则提示输入新密码 4
  • 配置apache2.4+PHP8.0(转)

    转自 xff1a 配置apache2 4 43 PHP8 0 chicboy2 博客园 cnblogs com xff08 1 xff09 下载apache2 4 按照电脑版本下载压缩包 xff08 2 xff09 下载后解压缩到需要安装的
  • error: cast from pointer to smaller type ‘unsigned int‘ loses information

    在使用clang编译一处代码时报标题中的错误 原因 xff1a 64为机器上因为int为4字节 xff0c 指针统统为8字节 xff0c int无法容纳一个指针的值 解决 xff1a 将 unsigned int 强制类型转换修改成 xff

随机推荐