这个内存对齐问题,居然影响到了sizeof(struct)的结果值。突然想到了之前写的一个API库里,有个API是向后台服务程序发送socket请求。其中的socket数据包是一个结构体。在发送socket之前,会检测数据的长度;服务端接收到数据后也会检测长度。如果说内存对齐问题影响到了结构体的sizeof,那么socket发送结构体的时候,是怎么发送的?发送的内容中是否包含结构体中的“空洞”?如果API库中的对齐方式没有设定,那么服务端和客户端的sizeof结果将不同,这会引起很多问题吗?

下面文字介绍了这种机制的原理。还没有针对该原理分析那个API库,回去要好好看一下。

转载自http://www.cppblog.com/xczhang/archive/2007/12/23/39396.html

首先请大家先看下面代码:

 typedef struct
{
UINT32 NumElements;
union
{
UINT32 ObjectHandle;
}Entry;
}STR_ARRAY, *PSTR_ARRAY;

还有这两句

    #pragma pack(push, 1)
#pragma pack(pop)

    #pragma  pack( [ n ] )

该指令指定结构和联合成员的紧凑对齐。而一个完整的转换单元的结构和联合的紧凑对齐由/ Z p 选项设置。紧凑对齐用p a c e 编译指示在数据说明层设置。该编译指示在其出现后的第一个结构或联合说明处生效。该编译指示对定义无效。当你使用#pragma  pack ( n ) 时, 这里n 为1 、2 、4 、8 或1 6 。第一个结构成员之后的每个结构成员都被存储在更小的成员类型或n 字节界限内。如果你使用无参量的#pragma  pack , 结构成员被紧凑为以/ Z p 指定的值。该缺省/ Z p 紧凑值为/ Z p 8 。

编译器也支持以下增强型语法:
    #pragma  pack( [ [ { p u s h | p o p
} , ] [ 标识符, ] ] [ n] )若不同的组件使用p a c k 编译指示指定不同的紧凑对齐,
这个语法允许你把程序组件组合为一个单独的转换单元。带p u s h 参量的p a c k
编译指示的每次出现将当前的紧凑对齐存储到一个内部编译器堆栈中。编译指示的参量表从左到右读取。如果你使用p u s h ,
则当前紧凑值被存储起来; 如果你给出一个n 的值, 该值将成为新的紧凑值。若你指定一个
标识符, 即你选定一个名称,
则该标识符将和这个新的的紧凑值联系起来。带一个p o p 参量的p a c k
编译指示的每次出现都会检索内部编译器堆栈顶的值,并且使该值为新的紧凑对齐值。如果你使用p o p
参量且内部编译器堆栈是空的,则紧凑值为命令行给定的值, 并且将产生一个警告信息。若你使用p o p 且指定一
个n 的值,
该值将成为新的紧凑值。若你使用p o p 且指定一个标识符,  所有存储在堆栈中的值将从栈中删除, 直到找到一个匹配的标识符,
这个与标识符相关的紧凑值也从栈中移出, 并且这个仅在标识符入栈之前存在的紧凑值成为新的紧凑值。如果未找到匹配的标识符,
将使用命令行设置的紧凑值, 并且将产生一个一级警告。缺省紧凑对齐为8 。p a c k 编译指示的新的增强功能让你编写头文件,
确保在遇到该头文件的前后的紧凑值是一样的。

什么是内存对齐

考虑下面的结构:

 struct foo
{
char c1;
short s;
char c2;
int i;
};

假设这个结构的成员在内存中是紧凑排列的,假设c1的地址是0,那么s的地址就应该是1,c2的地址就是3,i的地址就是4。也就是

c1 00000000, s 00000001, c2 00000003, i 00000004。

可是,我们在Visual c/c++ 6中写一个简单的程序:

        struct foo a;
printf("c1 %p, s %p, c2 %p, i %p\n",
(unsigned int)(void*)&a.c1 - (unsigned int)(void*)&a,
(unsigned int)(void*)&a.s - (unsigned int)(void*)&a,
(unsigned int)(void*)&a.c2 - (unsigned int)(void*)&a,
(unsigned int)(void*)&a.i - (unsigned int)(void*)&a);

运行,输出:

c1 00000000, s 00000002, c2 00000004, i 00000008。

为什么会这样?这就是内存对齐而导致的问题。

为什么会有内存对齐

以下内容节选自《Intel Architecture 32 Manual》。
    字,双字,和四字在自然边界上不需要在内存中对齐。(对字,双字,和四字来说,自然边界分别是偶数地址,可以被4整除的地址,和可以被8整除的地址。)
    无论如何,为了提高程序的性能,数据结构(尤其是栈)应该尽可能地在自然边界上对齐。原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;然而,对齐的内存访问仅需要一次访问。
    一个字或双字操作数跨越了4字节边界,或者一个四字操作数跨越了8字节边界,被认为是未对齐的,从而需要两次总线周期来访问内存。一个字起始地址是奇数但却没有跨越字边界被认为是对齐的,能够在一个总线周期中被访问。
   
某些操作双四字的指令需要内存操作数在自然边界上对齐。如果操作数没有对齐,这些指令将会产生一个通用保护异常(#GP)。双四字的自然边界是能够被16
整除的地址。其他的操作双四字的指令允许未对齐的访问(不会产生通用保护异常),然而,需要额外的内存总线周期来访问内存中未对齐的数据。

编译器对内存对齐的处理

缺省情况下,c/c++编译器默认将结构、栈中的成员数据进行内存对齐。因此,上面的程序输出就变成了:
c1 00000000, s 00000002, c2 00000004, i 00000008。
编译器将未对齐的成员向后移,将每一个都成员对齐到自然边界上,从而也导致了整个结构的尺寸变大。尽管会牺牲一点空间(成员之间有空洞),但提高了性能。
也正是这个原因,我们不可以断言sizeof(foo) == 8。在这个例子中,sizeof(foo) == 12。

如何避免内存对齐的影响

那么,能不能既达到提高性能的目的,又能节约一点空间呢?有一点小技巧可以使用。比如我们可以将上面的结构改成:

struct bar
    {
        char c1;
        char c2;
        short s;
        int i;
    };
    这样一来,每个成员都对齐在其自然边界上,从而避免了编译器自动对齐。在这个例子中,sizeof(bar) == 8。

这个技巧有一个重要的作用,尤其是这个结构作为API的一部分提供给第三方开发使用的时候。第三方开发者可能将编译器的默认对齐选项改变,从而造成这个结构在你的发行的DLL中使用某种对齐方式,而在第三方开发者哪里却使用另外一种对齐方式。这将会导致重大问题。
    比如,foo结构,我们的DLL使用默认对齐选项,对齐为
c1 00000000, s 00000002, c2 00000004, i 00000008,同时sizeof(foo) == 12。
而第三方将对齐选项关闭,导致
    c1 00000000, s 00000001, c2 00000003, i 00000004,同时sizeof(foo) == 8。

如何使用c/c++中的对齐选项

vc6中的编译选项有 /Zp[1|2|4|8|16] ,/Zp1表示以1字节边界对齐,相应的,/Zpn表示以n字节边界对齐。n字节边界对齐的意思是说,一个成员的地址必须安排在成员的尺寸的整数倍地址上或者是n的整数倍地址上,取它们中的最小值。也就是:
    min ( sizeof ( member ),  n)
    实际上,1字节边界对齐也就表示了结构成员之间没有空洞。
    /Zpn选项是应用于整个工程的,影响所有的参与编译的结构。
    要使用这个选项,可以在vc6中打开工程属性页,c/c++页,选择Code Generation分类,在Struct member alignment可以选择。

要专门针对某些结构定义使用对齐选项,可以使用#pragma pack编译指令。指令语法如下:
#pragma pack( [ show ] | [ push | pop ] [, identifier ] , n  )
    意义和/Zpn选项相同。比如:

    #pragma pack(1)
struct foo_pack
{
char c1;
short s;
char c2;
int i;
};
#pragma pack()

栈内存对齐

我们可以观察到,在vc6中栈的对齐方式不受结构成员对齐选项的影响。(本来就是两码事)。它总是保持对齐,而且对齐在4字节边界上。

请看下面的验证代码:

   #include <stdio.h>

     struct foo
{
char c1;
short s;
char c2;
int i;
}; struct bar
{
char c1;
char c2;
short s;
int i;
}; #pragma pack(1)
struct foo_pack
{
char c1;
short s;
char c2;
int i;
};
#pragma pack() int main(int argc, char* argv[])
{
char c1;
short s;
char c2;
int i; struct foo a;
struct bar b;
struct foo_pack p; printf("stack c1 %p, s %p, c2 %p, i %p\n",
(unsigned int)(void*)&c1 - (unsigned int)(void*)&i,
(unsigned int)(void*)&s - (unsigned int)(void*)&i,
(unsigned int)(void*)&c2 - (unsigned int)(void*)&i,
(unsigned int)(void*)&i - (unsigned int)(void*)&i); printf("struct foo c1 %p, s %p, c2 %p, i %p\n",
(unsigned int)(void*)&a.c1 - (unsigned int)(void*)&a,
(unsigned int)(void*)&a.s - (unsigned int)(void*)&a,
(unsigned int)(void*)&a.c2 - (unsigned int)(void*)&a,
(unsigned int)(void*)&a.i - (unsigned int)(void*)&a); printf("struct bar c1 %p, c2 %p, s %p, i %p\n",
(unsigned int)(void*)&b.c1 - (unsigned int)(void*)&b,
(unsigned int)(void*)&b.c2 - (unsigned int)(void*)&b,
(unsigned int)(void*)&b.s - (unsigned int)(void*)&b,
(unsigned int)(void*)&b.i - (unsigned int)(void*)&b); printf("struct foo_pack c1 %p, s %p, c2 %p, i %p\n",
(unsigned int)(void*)&p.c1 - (unsigned int)(void*)&p,
(unsigned int)(void*)&p.s - (unsigned int)(void*)&p,
(unsigned int)(void*)&p.c2 - (unsigned int)(void*)&p,
(unsigned int)(void*)&p.i - (unsigned int)(void*)&p); printf("sizeof foo is %d\n", sizeof(foo));
printf("sizeof bar is %d\n", sizeof(bar));
printf("sizeof foo_pack is %d\n", sizeof(foo_pack)); return ; }

C/C++中的内存对齐问题和pragma pack命令详解的更多相关文章

  1. yum(Fedora和RedHat以及SUSE中的Shell前端软件包管理器)命令详解

    yum官方网站:http://yum.baseurl.org/ Fedora对于yum的介绍:http://fedoraproject.org/wiki/Yum yum(全称为 Yellow dog ...

  2. C/C++中的内存对齐 C/C++中的内存对齐

    一.什么是内存对齐.为什么需要内存对齐? 现代计算机中内存空间都是按照byte划分的,从理论上讲似乎对任何类型的变量的访问可以从任何地址开始,但实际情况是在访问特定类型变量的时候经常在特 定的内存地址 ...

  3. C语言中的内存对齐

    最近看了好多,也编了好多C语言的浩强哥书后的题,总觉的很不爽,真的真的好怀念linux驱动的代码,好怀念那下划线,那结构体,虽然自己还很菜. 同时看了一遍陈正冲老师的C语言深度剖析,收益很多,又把唐老 ...

  4. C++继承体系中的内存对齐

    本篇随笔讨论一个比较冷门的知识,继承结构中内存对齐的问题,如今内存越来越大也越来越便宜,大部分人都已经不再关注内存对齐的问题了.但是作为一个有追求的技术人员,实现功能永远都是最基本的要求,把代码优化到 ...

  5. 配置tomcat服务器内存大小中的Xms、Xmx、PermSize、MaxPermSize 详解

    1.参数的含义 -vmargs -Xms256m -Xmx512m -XX:PermSize=256M -XX:MaxPermSize=512M -vmargs 说明后面是VM的参数,所以后面的其实都 ...

  6. java使用POI操作XWPFDocument中的XWPFRun(文本)对象的属性详解

    java使用POI操作XWPFDocument中的XWPFRun(文本)对象的属性详解 我用的是office word 2016版 XWPFRun是XWPFDocument中的一段文本对象(就是一段文 ...

  7. oracle中imp命令详解 .

    转自http://www.cnblogs.com/songdavid/articles/2435439.html oracle中imp命令详解 Oracle的导入实用程序(Import utility ...

  8. PHP中IP地址与整型数字互相转换详解

    这篇文章主要介绍了PHP中IP地址与整型数字互相转换详解,本文介绍了使用PHP函数ip2long与long2ip的使用,以及它们的BUG介绍,最后给出自己写的两个算法,需要的朋友可以参考下 IP转换成 ...

  9. ArcGIS中的北京54和西安80投影坐标系详解

    ArcGIS中的北京54和西安80投影坐标系详解 1.首先理解地理坐标系(Geographic coordinate system),Geographic coordinate system直译为地理 ...

随机推荐

  1. Decision tree(决策树)算法初探

    0. 算法概述 决策树(decision tree)是一种基本的分类与回归方法.决策树模型呈树形结构(二分类思想的算法模型往往都是树形结构) 0x1:决策树模型的不同角度理解 在分类问题中,表示基于特 ...

  2. 机器学习 - 损失计算-softmax_cross_entropy_with_logits

    tf.nn.softmax_cross_entropy_with_logits(logits, labels, name=None) 第一个参数logits:就是神经网络最后一层的输出 第二个参数la ...

  3. 【Java面试题】19 final,finally和finalize的区别

    总体区别 final       用于申明属性,方法和类,表示属性不可变,方法不可以被覆盖,类不可以被继承.finally     是异常处理语句结构中,表示总是执行的部分. finallize   ...

  4. postgreSql 常用操作总结

    0. 启动pgsl数据库 pg_ctl -D /xx/pgdata start 1. 查看pgsl版本 pg_ctl --version 1. 命令行登录数据库 psql -U username -d ...

  5. MongoDB3.6 一键化自动部署方案

    1.系统基础配置 下面的命令默认都使用root用户进行操作,操作系统为Centos7,mongodb3.6.x以上版本 1.1 修改系统配置文件/etc/security/limits.conf和/e ...

  6. pc端,移动端css重置样式

    pc: html{font-family:"Microsoft YaHei UI","Microsoft YaHei",sans-serif;-ms-text- ...

  7. EasyUI datagrid 的多条件查询

    <script type="text/javascript">         $(function () {            $("#dg" ...

  8. 402 CSS菜鸟:transform and transition

    <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title> ...

  9. 「luogu1417」烹调方案

    题目链接 :https://www.luogu.org/problemnew/show/P1417 直接背包 ->  30' 考虑直接背包的问题:在DP时第i种食材比第j种食材更优,但由于j&l ...

  10. Selenium Locating Elements

    Locating Elements Location Methods: find_element_by_id find_element_by_name find_element_by_xpath fi ...