(整理)字节序、字节对齐的理解.

(整理)字节序、字节对齐的理解.
(整理)字节序、字节对齐的理解.

1、前言

作为一名 C/C++ 程序员,字节是我们天天都要与之打交道的一个东西。我们和它熟稔到几乎已经忘记了它的存在。可是,它自己是不甘寂寞的,或迟或早地,总会在某些时候探出头来张望,然后给你一个腿儿绊。其实,只要你真正了解了它的底细,你就会畅行无阻。在本文中,我们将首先简要了解一下字节的概念,然后着重了解一下字节序问题和字节对齐问题。

2、什么是字节

我们知道,二进制计算机(也就是我们目前接触到的几乎所有的计算机)的最小数据单位是位( bit )。一位数据只能够表示两种含义(需要说明,尽管我们通常把单个位表示的两种含义选择为相互对立的含义,但这并不是必然的,例如你可以认为 1 代表 5 个人, 0 代表 8 个人),对于绝大多数的计算要求,单个位显然不能满足。因此,我们通常都会使用一连串的位,我们可以称之为位串( bit string ,请爱好质疑的的朋友注意,此术语非我杜撰)。由于种种原因,计算机系统都不会让你使用任意长度的位串,而是使用某个特定长度的位串。一些常见的位串长度形式具有约定好的名称,如,半字节( nibble ,貌似用的不多)代表四个位的组合,字节( byte ,主角出场!)代表 8 个位的组合。再多的还有,字( word )、双字( Double word ,通常简写为 Dword )、四字( Quad word ,经常简写为 Qword )、十字节( Ten byte ,也简写为 Tbyte )。

在这些里面,字( word )有时表示不同的含义。在 Intel 体系里, word 表示一个 16 位的数值,它是固定大小的。而在另外一些场合, word 表示了 CPU 一次可处理的数据的位数,表示一个符合 CPU 字长( word-length )的数目的位串。事实上我们接触较多的 ARM 体系中, word 就有不同的含义,它表示一个 32 位的数据(与机器字长相同),对于 16 位大小的数据, ARM 使用了另外的一个术语,叫作半字( half-word ),请大家在文档阅读时加以注意。另外, Qword 也是 Intel 体系中的术语,其他的体系中可能并不使用。在本文中,我们按照 Intel 的惯例来使用字或者 word 这一术语。

一个字节中共有 8 个数据位,有时需要用图表逐位表述各个位。习惯上,我们按照下面的图来排列各个位的顺序,即,按照从右到左的顺序,依次为最低位(从第 0 位开始)到最高位(对于字节,则是第 7 位):

字节是大多数现代计算机的最小存储单元,但这并不代表它是计算机可以最高效地处理的数据单位。一般的来说,计算机可以最高效地处理的数据大小,应该与其字长相同。在目前来讲,桌面平台的处理器字长正处于从 32 位向 64 位过渡的时期,嵌入式设备的基本稳定在 32 位,而在某些专业领域(如高端显卡),处理器字长早已经达到了 64 位乃至更多的 128 位。

3、字节序问题的由来

对于字、双字这些多于一个字节的数据,如果把它们放置到内存中的某个位置上,可以看出,我们还可以将之看作是字节的序列。一个字是两个字节,双字则是四个字节。假设有以下数据: 0x12345678 、 0x9abcdef0 。在此处,我使用了我们最习惯的十六进制表示法,并给出了两个双字的值。按照惯例,我把双字的左侧视为高端,而把右侧视为低端。把它们顺序放置在起始地址为 0 的内存中,如下图所示:

由图示可知, 0x9abcdef 的相应地址为 0x04 。现在,问题来了,如果有一个内存操作,要从地址 0x06 处读取一个字,得到的结果是多少呢?答案是:不一定。

这里的本质问题在于,如何把多字节的对象存储到内存中去呢?即使使用最正常的思维去考虑这个问题,你也会发现有两种方法。第一种方法是,把最低端的字节放到指定的起始位置(即基地址处),然后按照从低到高的字节顺序把其余字节依次放入,如下图 a ;另一种方法非常类似,但是对高端字节和低端字节的处理顺序正好相反,如下图 b (我确信你还可以想出其他的方法,但是除二字节的情况外,必然会打破字节排列顺序的一致性,我视之为反常规思维的产物,此处暂不考虑)。

图 a

图 b

在很久之前,哪一种存储方式更为合理曾经有过争论。到今天,争论的结果已经无关紧要了,紧要的是以下事实:这两种存储方式都被应用到了现实的计算机系统中。上图 a 中的排列方式为 Intel 所采用并大行其道,而图 b 的排列方式则被大多数的其他平台采用(如最近被苹果公司彻底抛弃的 PowerPC ),因此

上,我们不能称之为罕见的用法。之所以造成事实上的不经常见到,其原因正如我今天中午所得到的消息: Intel 的 CPU 占整个市场份额的 80% 以上。

这两种排列方式通常用小端( little endian )和大端( big endian )来称谓。这两个奇怪的名字据说来源于童话《格列佛游记》,其中小人国里的公民为了鸡蛋到底是应该从小的一头打开还是大的一头打开而大起争执。 Intel 的方式对应于“小端”,顺便说一句,大端的方式也有一个大公司的名字作为其代表,即最近开始没落的 Motorola 。如果有谁了解过 TIFF 图像文件格式,就会发现其文件头中用以标识文件数据字节序的标志就是“ II ”和“ MM ”,分别对应于 Intel 和 Motorola 的首字母。值得提醒一下,小端方式的排列与位的排列顺序相一致,看上去似乎更协调一些。

现在我们可以回答上面的问题了。对于小端字节序,我们取到的字,其值为

0x9abc ,而如果是大端字节序的话,就会取到 0xdef0 。

4、何时会出现字节序问题

字节序问题主要出现在数据在不同平台之间进行交换时,交换的途径可能是网络传输,也可能是文件复制。例如,如果你设计了一种可能会应用于不同平台的文件格式,其中存储了某些数据结构,则对于大小大于一个字节的数据就要明确地规定其遵循的字节序,以便各平台上的处理程序可以在使用数据时实现做必要的转换。

举一个实际的例子。 Java 是一个跨平台的编程语言,其可执行文件(扩展名

为 .class ,使用的是一种机器无关的字节码指令集)在理论上可以运行于所有的实现了 Java 运行时的平台(包含有与特定平台相关特性的除外)。编译后

的 .class 中一定保存有诸如 Integer 这样类型的数据,这就涉及到了字节序的确定,否则 .class 必然不能被采用了不同字节序的平台同时正确加载并运行。事实上, Java 语言采用的为大端字节序,这个一点都不奇怪,因为当初 SUN 公司自己的 SPARC 架构就是采用的大端字节序。同样的问题和解决问题的方式,也存在于操作系统新贵 android 系统上。

网络传输则是另一个典型场景。 TCP/IP 所采用的网络传输字节序标准也是大端字节序,这个也不必奇怪,因为 TCP/IP 是从 UNIX 系统发展起来的,而绝大部分的 UNIX 系统在很长的一段时间内都没有运行于 Intel 体系架构上的版本。

处理字节序问题的手段非常简单,也就是对数据进行必要的转换:将十六进制的数字从两端开始交换,直至移动到数据的中心,交换完成为止。交换的结果就好像物体与镜面之内的成像互换了位置,因此也被称为镜像交换( mirror-image swap )。请参看下图:

5、如何在程序中判断字节序

在实际的工作中,有时需要对字节序进行判断,然后予以不同的处理。一般的来说,编译后的程序通常只能运行在特定的平台之上,其所采用的字节序方式在编译时即可确定,在这种情况下,程序源代码中通常是把字节序的判别作为条件编译的判断语句,而不会判断代码放在真正的可执行代码中。

在这里,需要使用我们的老朋友——宏。以下是一个真实的跨平台工程中代码,清晰起见,我稍做了修改:

#define SGE_LITTLE_ENDIAN 1234

#define SGE_BIG_ENDIAN 4321

#ifndef SGE_BYTEORDER

#if defined(__hppa__) || \

defined(__m68k__) || defined(mc68000) || defined(_M_M68K) || \ (defined(__MIPS__) && defined(__MISPEB__)) || \

defined(__ppc__) || defined(__POWERPC__) || defined(_M_PPC) || \ defined(__sparc__)

#define SGE_BYTEORDER SGE_BIG_ENDIAN

#else

#define SGE_BYTEORDER SGE_LITTLE_ENDIAN

#endif

#endif

以上为根据平台的预定义宏所作的前期工作,将之存入一个头文件中,然后包含到源代码文件中使用。

在需要进行判断的时候,则像以下代码这样使用:

#if SGE_BYTEORDER == SGE_BIG_ENDIAN

#define SwapWordLe(w) SwapWord(w)

#else

#define SwapWordLe(w) (w)

#endif

由于这两个宏实际上被定义成了常量数值,因此也可以被用到可执行代码中,进行执行期的动态判断:

if(SGE_BYTEORDER == SGE_BIG_ENDIAN)

return r << 16 | g << 8 | b;

else

return r | g << 8 | b << 16;

追根寻源,上面的这种判断需要依赖编译器及其所在平台的预定义宏。下面介绍一种执行期动态判断的方法,则不需要有宏的参与,而是巧妙地利用了字节序的本质。代码如下:

int IsLittleEndian()

{

const static union

{

unsigned int i;

unsigned char c[4];

} u = { 0x00000001 };

return u.c[0];

}

动手画一下内存布局即可了解其原理。还有更简练的写法,作为练习,请大家自行去寻找。

在结束对字节序的讨论之前,特别提醒一下, ARM 体系的 CPU 在字节序上与Intel 的体系结构是一致的。

6、字节对齐问题的产生

冯诺依曼体系的计算机,通过地址总线来寻址内存(假设 n 为地址总线的位数,则最多可以寻址 2n 个内存位置)。根据地址总线的位数,我们可以知道 CPU 与内存的一次交互(也即一次内存访问)能够读写的数据的大小。显然地,对于 8 位的 CPU ,是一个字节,对于 16 位 CPU 则是一个字, 32 位 CPU 则是一个双字,依此类推。这是 CPU 与生俱来的最本质、最快捷的访问方式。在实际的计算需求中,如果访问的数据量超过了一次访问的限度,则很显然需要进行多次访问,如果是少于的话,则需要对从内存中取回的数据进行适当的裁剪。裁剪操作有可能是 CPU 自身支持的,也有可能是需要用软件来实现的。

有的系统是支持寻址到单个字节所在的位置的(称为可字节寻址),而有的则不可以,只能寻址到符合某些条件的地址上。对于 Intel/ARM 体系结构的 CPU ,我们在宏观上可以认为它们都支持字节寻址(但是 ARM 家族的 CPU 在内存访问时有其他约束,下文有详细叙述)。

出现这样的限制是有原因的,终极因素就在于内存访问的粒度与字长的关联上。用 32 位 CPU 来说,它对于地址为 4 的倍数处的内存访问是最自然的,其余的地址就要做一些额外的工作。例如,我们要访问地址为 0x03 处的一个双字,对

于 80x86 体系,事实上将会导致 CPU 的两次内存访问,取回 0x00 以及 0x04 处的两个双字,分别进行适当的截取之后再拼装为一个双字返回。对于其他的体系,设计者可能认为 CPU 不应该承担数据拼装的工作,因而就选择产生一个硬件异常。

在硬件和 / 或操作系统的约束下,进行数据访问时对数据所在的起始位置以及数据的大小都需要遵循一定的规则,与这些规则相关的问题,都可以称之为字节对齐问题。

举例来说。在 HP-UX (惠普公司的一个服务器产品平台, UNIX 的一种)平台中,系统严禁对奇地址直接进行访问,假设你视这一原则于不顾:

int i = 0; // 编译器保证 i 的起始地址不是奇地址

char c = *((char*)&i + 1); // 强制在奇地址处访问

其执行结果就是内核转储( core dump ),为应用程序最严重的错误。(特别注明:此处代码为记忆中的情形,目前笔者已经没有验证环境了)

在不同的硬件体系架构下,字节对齐关系到三方面的问题,一是数据访问的可行性问题,二是数据访问的效率问题,三是数据访问的正确性问题。

字节对齐问题给程序员在编码时带来了额外的注意点,并且对最终程序执行的正确性也带来了一定的不确定因素。相同的代码在不同的平台上,甚至在相同的平台上采用不同的编译选项,都可能有不同的执行结果。

如果所有的系统都和 HP-UX 的表现一样的话,事情要简单一些,问题通常会在比较早的时间内就可以暴露出来。遗憾的是,我们目前所面对的平台不是这样,这些平台的设计者为最大程度地减少对开发人员的干扰而作了辛苦的努力,使得我们在很多时候都感觉不到字节对齐问题的存在。但另一方面,也制造出了把问题隐藏得更深的机会。

效果最好的努力是 Intel 的体系架构。 80x86 允许你对整个内存进行字节寻址,在不超过机器字长的情况下可以访问任意数目的字节(很显然,大多数情况下就是 1 字节、 2 字节、 3 字节、 4 字节这四种情况)。

ARM 体系的 CPU 似乎做了一定的努力,但是其结果和其他体系相比呈现一种很奇怪的状态。由于笔者没有对 ARM 整个系列的 CPU 进行过完整的了解,因此此处的论述可能并不完整。 ARM CPU 允许对内存进行字节寻址,但在访问时有额外的要求。即:如果你要访问一个字(注意本文惯例,此处的字是两字节大小,与 ARM 平台的标准术语不同),那么起始地址必须在一个字的边界上,如果访问一个双字,则起始地址必须位于一个双字的边界上(其余数据类型请参考 ARM 的知识库文档)。这意味着,你不能在 0x03 这样的地址处访问一个字或者一个双字。但是,令人痛苦的事情到来了,如果你非要这么访问,大多数的 CPU 不会有显式的异常,而是返回错误的数据,其余的一些 CPU 则会造成程序崩溃。

7、如何控制字节对齐

控制程序的字节对齐行为是一个与编译器相关的工作。以下编译指示

( directive )被许多编译器认可:

#pragma pack(n)

#pragma pack()

任何处于这两个编译指示语句之间的数据结构,将采用 n 字节的数据对齐方式。n 是一个可以指定的数字,取值范围请参阅所使用编译器的文档,通常都会取值为 2 的幂。现代编译器在对程序进行编译时,处于效率方面的考虑,会对数据结构的内存布局使用一个默认的字节对齐值,这个值一般都可以在命令行上显式指定。如果要在一个头文件 / 源文件中对特定的部分指定对齐属性,则需要上述的编译指示。结束指示的写法在某些编译器或者平台下需要写成:

#pragma pack(pop)

我们用一个例子来看一下这两个指示的实际效用,看它究竟是如何影响数据的内存排列的。假定我们有如下的数据结构定义:

struct S1

{

int i;

char c;

short s;

};

struct S2

{

char c;

int i;

short s;

};

这两个结构的成员看起来是一样的,只不过换了一下顺序而已。我们使用

sizeof() 操作符来测量各自占用多少字节(除非特别指出,均在 32 位平台上,并认为 int 占用 4 字节, char 占用 1 字节, short 占用 2 字节)。答案似乎不可思议, sizeof(S1) 的结果是 8 ,而 sizeof(S2) 却是 12 。差异是怎么来的呢?原因就在于编译器缺省的字节对齐设定在发生作用。

这里需要引入以下概念和规则:

概念及规则一,原生数据类型自身对齐值。原生数据类型即是 C/C++ 直接支持的数据类型,也可以称为内建( built in )数据类型。它们的自身对齐值分别

为: char 为 1 , short int 为 2 , int 、 float 、 double 等为 4 ,不受符号位(即正负)的影响。

概念及规则二,用户数据类型自身对齐值。用户数据类型即由程序员定义的类、结构、联合等,也叫抽象数据类型( ADT )。它们的自身对齐值等同于为其成员的对齐值中的最大值。

概念及规则三,用户指定对齐值。程序员在编译器命令行上的指定值,或者在pragma pack 编译指示中指定的值,对最终数据的影响取就近原则(显然 pragma pack 指示会覆盖命令行的指定)。

概念及规则四,有效对齐值。取数据类型的自身对齐值与用户指定对齐值中的较小值。此值一旦决出,则会影响到数据在内存中的布局。一个有效对齐值为 n ,表示以下事实:相关数据在内存中存放时,其起始地址的值必须可以被 n 整除。

根据以上四条,可以很圆满地解释 S1 和 S2 的大小不同这一现状。由于没有使用 pragma pack 指示,那么编译器(在我的测试环境下)会采用缺省的对齐值 4 。假设 S1 或者 S2 的实例将从地址 0x0000 处开始。

在 S1 中,第一个成员 i 的自身对齐值为 4 ,指定对齐值(尽管是缺省的)也是 4 ,同时 0x0000 这一地址符合被 4 整除的要求,因此, i 将占据 0x0000 到 0x0003 的四个字节,下一个可用地址值为 0x0004 ;接下来的成员 c 的数据类型为 char ,自身对齐值为 1 ,指定对齐值为 4 ,取较小者仍然是 1 ,

0x0004 符合被 1 整除的要求,因此 c 将占据 0x0004 处的一个字节,下一个可用地址值为 0x0005 ;最后的一个成员 s 数据类型为 short ,自身对齐值为

2 ,指定对齐值为 4 ,有效对齐值取 2 ,但是地址 0x0005 不能符合被 2 整除的要求,因此编译器作相应调整,向后移动到最近的满足要求的地址处,即

0x0006 , s 将占用 0x0006 和 0x0007 处的两个字节,由此导致 S1 的大小为

8 。

在地址 0x0005 处的一个字节,习惯上称之为填充数据( padding )。

同理可以轻易推出 S2 结构的大小确实是 12 。是这样吗?不是的。实际动手的结果应该是 10 。那么 12 应该作何解释?

我们来设想一个场景,程序员用 new 或者 malloc 分配一个 S2 的数组。不用多,假定有两个元素,而地址 0x0000 处正好有空闲的内存可以满足这一内存分配请求。我们都知道,在 C/C++ 语言中,数组的元素是紧邻排放的。也就是说,后一个元素的起始地址应该正好等于前一个元素的起始地址,并加上元素的大小。我们来检视一下 S2 的情况,它的元素大小为 10 ,它的有效对齐值是 4 (请参阅概念及规则二),这表示任何一个 S2 结构的起始地址都应该位于 4 的整数倍处。现实的情况是,第一个元素的起始地址是 0x0000 ,第二个元素的起始地址变成了 0x000A ,而后者的数值不能满足被 4 整除的要求。正是为了解决这一情况,编译器为 S2 结构在结尾处也增加了两个字节的填充,从而满足各个条件的限定。

pragma pack 指示非常有效,使用也比较普遍,但是对于 ARM 平台,它有一些力所不及的地方,我们再来看一个例子。仍然用 S2 ,这一次,我们强制把它的字节对齐设定为 1 ,并同时定义了 S2 的一个全局变量 s2 。也即:

#pragma pack(1)

struct S2

{

char c;

int i;

short s;

} s2;

#pragma pop()

然后,在某处具有如下的数据访问:

int i = s2.i;

这条看上去稀松平常的语句很可能不能如所希望的那样执行。因为对于 i 的访问其前提应该是 i 的起始地址是 4 的倍数(注意,这个不是对齐规则的约束结果,而是 ARM CPU 的数据访问规则的约束结果),但强行指定的 1 字节对齐则导致 i 的起始地址是一个奇数。

RVCT 编译器为此做了特别的努力,引入了 __packed 关键字。此关键字应用到用户定义数据结构上会导致该结构的内存布局取得与 pragma pack(1) 等同的

效果,但是,更进一步地,编译器会把对该结构中成员的访问作适当的处理,发现不对齐的访问则会翻译为调用适当的保证数据正确性的函数。此关键字也可

以应用到指针上,以保证经由指针对目标对象的访问也采用保守方式。可以预料到的是,此关键字的使用会降低代码执行的效率,所以需要慎用,一个很典型的使用场景是移植其他平台的代码时。以下是一些使用了此关键字的定义示例:

typedef __packed struct

{

char x; // 所有成员都会被 __packed 修饰

int y;

} X; // 5 字节的结构,自身对齐值= 1

int f(X* p)

{

return p->y; // 执行一个非对齐的读取操作

}

typedef struct

{

short x;

char y;

__packed int z; // 仅 __pack 本成员,此用法仅适用于整型 char a;

} Y; // 8 字节结构,自身对齐值 = 2(请思考原因)

int g(Y* p)

{

return p->z + p->x; // 仅对 z 执行非对齐读取操作

}

需要注意的是, GCCE 编译器没有实现类似的努力,它有一个和对齐有关的关键字: __attribute__ (packed)) ,该关键字的功效与 pragma pack(1) 类似。

H.264码流结构解析

H.264码流结构解析 1.H.264简介 MPEG(Moving Picture Experts Group)和VCEG(Video Coding Experts Group)已经联合开发了一个比早期研发的MPEG 和H.263性能更好的视频压缩编码标准,这就是被命名为A VC(Advanced Video Coding),也被称为ITU-T H.264建议和MPEG-4的第10 部分的标准,简称为H.264/A VC或H.264。这个国际标准已经与2003年3月正式被ITU-T所通过并在国际上正式颁布。为适应高清视频压缩的需求,2004年又增加了FRExt部分;为适应不同码率及质量的需求,2006年又增加了可伸缩编码SVC。 2.H.264编码格式 H.263定义的码流结构是分级结构,共四层。自上而下分别为:图像层(picturelayer)、块组层(GOB layer)、宏块层(macroblock layer)和块层(block layer)。而与H.263相比,H.264的码流结构和H.263的有很大的区别,它采用的不再是严格的分级结构。 H.264支持4:2:0的连续或隔行视频的编码和解码。H.264压缩与H.263、MPEG-4相比,视频压缩比提高了一倍。 H.264的功能分为两层:视频编码层(VCL, Video Coding Layer)和网络提取层(NAL, Network Abstraction Layer)。VCL数据即编码处理的输出,它表示被压缩编码后的视频数据序列。在VCL数据传输或存储之前,这些编码的VCL数据,先被映射或封装进NAL单元中。每个NAL单元包括一个原始字节序列负荷(RBSP, Raw Byte Sequence Payload)、一组对应于视频编码的NAL头信息。RBSP的基本结构是:在原始编码数据的后面填加了结尾比特。一个bit“1”若干比特“0”,以便字节对齐。 图1 NAL单元序列 3.H.264传输 H.264的编码视频序列包括一系列的NAL单元,每个NAL单元包含一个RBSP,见表1。编码片(包括数据分割片IDR片)和序列RBSP结束符被定义为VCL NAL单元,其余为NAL单元。典型的RBSP单元序列如图2所示。每个单元都按独立的NAL单元传送。单元的信息头(一个字节)定义了RBSP单元的类型,NAL单元的其余部分为RBSP数据。 图2 RBSP序列举例

内存对齐方式

对齐方式 为什么会有内存对齐? 在结构中,编译器为结构的每个成员按其自然对界(alignment)条件分配空间;各个成员按照它们被声明的顺序在内存中顺序存储,第一个成员的地址和整个结构的地址相同。在缺省情况下,C编译器为每一个变量或数据单元按其自然对界条件分配空间。 字,双字,和四字在自然边界上不需要在内存中对齐。(对字,双字,和四字来说,自然边界分别是偶数地址,可以被4整除的地址,和可以被8整除的地址。)无论如何,为了提高程序的性能,数据结构(尤其是栈)应该尽可能地在自然边界上对齐。原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;然而,对齐的内存访问仅需要一次访问。 一个字或双字操作数跨越了4字节边界,或者一个四字操作数跨越了8字节边界,被认为是未对齐的,从而需要两次总线周期来访问内存。一个字起始地址是奇数但却没有跨越字边界被认为是对齐的,能够在一个总线周期中被访问。 某些操作双四字的指令需要内存操作数在自然边界上对齐。如果操作数没有对齐,这些指令将会产生一个通用保护异常(#GP)。双四字的自然边界是能够被16整除的地址。其他的操作双四字的指令允许未对齐的访问(不会产生通用保护异常),然而,需要额外的内存总线周期来访问内存中未对齐的数据。 影响结构体的sizeof的因素: 1)不同的系统(如32位或16位系统):不同的系统下int等类型的长度是变化的,如对于16位系统,int的长度(字节)为2,而在32位系统下,int的长度为4;因此如果结构体中有int等类型的成员,在不同的系统中得到的sizeof值是不相同的。 2)编译器设置中的对齐方式:对齐方式的作用常常会让我们对结构体的sizeof 值感到惊讶,编译器默认都是8字节对齐。 对齐: 为了能使CPU对变量进行高效快速的访问,变量的起始地址应该具有某些特性,即所谓的“对齐”。例如对于4字节的int类型变量,其起始地址应位于4字节边界上,即起始地址能够被4整除。变量的对齐规则如下(32位系统)

lwip-mem_init和mem_malloc详解

lwip-mem_init和mem_malloc详解 [cpp] view plain copy <pre name="code" class="cpp">#define MEM_ALIGNMENT 4 //对齐方式为4字节对齐#ifndef LWIP_MEM_ALIGN_SIZE #define LWIP_MEM_ALIGN_SIZE(size) (((size) + MEM_ALIGNMENT - 1) & ~(MEM_ALIGNMENT-1)) //实现待分配数据空间的内存对齐#endif #ifndef LWIP_MEM_ALIGN //地址对齐,对齐方式也为4字节对齐#define LWIP_MEM_ALIGN(addr) ((void *)(((mem_ptr_t)(addr) + MEM_ALIGNMENT - 1) & ~(mem_ptr_t)(MEM_ALIGNMENT-1))) #endif /* MEM_SIZE: the size of the heap memory. If the application will send a lot of data that needs to be copied, this should be set high. */ #define MEM_SIZE (8*1024) //堆的总空间大小,此后在这个基础上划分堆,将在这个空间进行内存分配,内存块结构体和数据都是在这个空间上的 //mem为内存块的结构体,next;,prev都为内存块索引struct mem { /** index (-> ram[next]) of the next struct */ //ram为堆的首地址,相当于数组的首地址,索引基地址mem_size_t next; //next为下一个内存块的索引/** index (-> ram[next]) of the next struct */

Altera的FPGA_常见问题汇总

常见问题汇总 1. alt2gxb模块的每个发送端都需要一个高速的pll_inclk时钟(至少100M以上),请问这个时钟一定要从FPGA外面引进来吗? 通常情况下一定要从FPGA外面引进来,首选是GXB模块的专用时钟引脚,或上下BANK 的专用时钟输入脚。时钟是至少60M以上。 2. 如果我一个FPGA里面有多个alt2gxb模块,是否能共用一个这样的输入时钟? 可以。 3. gxb模块里面的Calibration clk 是干嘛用的,能不能不用它? 校准内部匹配电阻用。此时钟可以内部提供,频率在10M到125M都可以,如果外部时钟不合适的话,甚至可以用逻辑来分频(比如参考钟是156M,内部触发器作个2分频就可以用了。 4. 用到gxb模块的bank的参考电压是否必须接1.5V?因为我看到资料上有3.3V的CML 和LVDS电平(附件里面的截图) gxb用1.5V 或 1.2V, 推荐客户用1.5V. 3.3v是用在别的普通bank的。 5. gxb模块的输入端如果平时不需要传数据,是否置0?还是需要我们在数据线上发送别的数据,是否gxb模块能自动发送同步码? 平时可以置0,但在上电后,你必须首先发送对端接收侧的word aligner码型(通常用k28.5), 这是需要手工控制的。 6. LVDS模块没有同步码,做接收时好像没办法数据对其,比如8比特数据容易错开2、3位,我们现在是另外加逻辑把它调整过来的,请问有别的好的同步的方法吗 通常需要逻辑去进行word aligner操作,如同GXB一样。某些特定情况下可以预先知道边界。这个问题讨论过好多次了,所谓的特定情况你可以看STRATIX II手册(不是Stratix II GX 手册),搜索“Differential I/O Bit Position” 7.请问在alt2gxb模块,有两个时钟:pll_inclk和cali_clk,手册上说cali_clk要求不是很高,可以用计数器产生,那么输入的并行数据txdata_in应该用哪个时钟锁存呢? cali_clk仅用于校准内部匹配电阻用的状态机,跟业务是完全独立的。txdata_in应该用 tx_clkout锁存。 8.pll_inclk可不可以用内部锁相环产生,然后输出经过一个差分时钟驱动,再送到gxb所在bank的REFCLK引脚?或者直接内部锁相环产生,直接送给gxb模块使用? 出于时钟质量考虑,我们不推荐用FPGA内部的锁相环来提供GXB的参考时钟,尤其是2SGX工作在3Gbps以上时。 速率低时如果客户一定要用PLL级联,在quartus.ini文件(注意该文件不是自动产生的,需要用户自己创建,放在当前工程根目录下)中包含下面这句话,如你描述的通过外部走线绕一下提供参考时钟没有必要。 siigx_allow_pll_cascade_to_tx_pll=on 9.在仿真时我直接加入激励数据给发送模块,它的串行输出再直接复制给接收模块,可是没有任何结果,请问有没有一种有效的仿真方法来仿真alt2gxb模块? 仿真时你需要激励一下powerdown信号,起始给高电平,过一会儿拉低。同时提供准确频率的参考时钟。 10.如果某个bank用到了LVDS模块,是不是这个bank的参考电压应该接2.5V,而IO电压仍然3.3V? 对lvds, IO电压是3.3V,参考电压不需要提供 11. 我在130 II gx里面放了几个GXB模块,设置的是100M输入时钟,数据率4G,线宽是32位,这样模块就没有rx_outclk这个信号线了,那么receiver的输出数据靠哪个时钟来锁呢? 你把rate matcher那个功能取消掉就可以由rx_outclk的输出了 12. 还有综合的时候报错说:

STM32启动文件详解

STM32启动文件详解 (2012-07-28 11:22:34) 转载▼ 分类:STM32 标签: stm32 启动 在<>,用的是STM32F103RBT6,所有的例程都采用了一个叫STM32F10x.s的启动文件,里面定义了STM32的堆栈大小以及各种中断的名字及入口函数名称,还有启动相关的汇编代码。STM32F10x.s是MDK提供的启动代码,从其里面的内容看来,它只定义了3个串口,4个定时器。实际上STM32的系列产品有5个串口的型号,也只有有2个串口的型号,定时器也是,做多的有8个定时器。比如,如果你用的 STM32F103ZET6,而启动文件用的是STM32F10x.s的话,你可以正常使用串口1~3的中断,而串口4和5的中断,则无**常使用。又比如,你TIM1~4的中断可以正常使用,而5~8的,则无法使用。 而在固件库里出现3个文件 startup_stm32f10x_ld.s startup_stm32f10x_md.s startup_stm32f10x_hd.s 其中,ld.s适用于小容量产品;md.s适用于中等容量产品;hd适用于大容量产品; 这里的容量是指FLASH的大小.判断方法如下: 小容量:FLASH≤32K 中容量:64K≤FLASH≤128K 大容量:256K≤FLASH ;******************** (C) COPYRIGHT 2011 STMicroelectronics ******************** ;* File Name : startup_stm32f10x_hd.s ;* Author : MCD Application Team ;* Version : V3.5.0 ;* Date : 11-March-2011 ;* Description : STM32F10x High Density Devices vector table for MDK-ARM ;* toolchain. ;* This module performs: ;* - Set the initial SP ;* - Set the initial PC == Reset_Handler ;* - Set the vector table entries with the exceptions ISR address ;* - Configure the clock system and also configure the external ;* SRAM mounted on STM3210E-EVAL board to be used as data ;* memory (optional, to be enabled by user) ;* - Branches to __main in the C library (which eventually ;* calls main()). ;* After Reset the CortexM3 processor is in Thread mode,

字节对齐

字节对齐 Andrew Huang 内容提要 ●字节对齐概念 ●字节对齐测试 ?offsetof ?缺省情况的字节对齐 ?double 型字节对齐 ?改变字节对齐设置 ●不同环境下的字节对齐 ?GCC字节对齐 ?ADS 字节对齐 ●字节对齐练习 字节对齐是一个很隐含的概念,平时可能你没有留意,但是如果你在编写网络通讯程序或者用结构去操作文件或硬件通讯结构,这个问题就会浮出水面。我记得第一次导致我去看字节对齐概念资料的原因就是ARP通讯,ARP包头是一个31Byte包头。当你用一个认为是31Byte结构去处理数据包时,却总是处理不对。这一篇文章详细讨论了字节对齐要领和各种情况. 字节对齐概念 ●现代计算机中内存空间都是按照byte划分的,从理论上讲似乎对任何类型的变量的访问可以从任何地址开始,但为了CPU访问数据的快速,通常都要求数据存放的地址是有一定规律的. ●比如在32位CPU上,一般要求变量地址都是基于4位,这样可以保证CPU用一次的读写周期就可以读取变量.不按4位对齐,如果变量刚好跨4位编码,这样需要CPU两个读写周期.效率自然低下.因此,在现代的编译器都会自动把复合数据定义按4位对齐,以保证CPU以最快速度读取,这就是字节对齐(byte Alignment)产生的背景 ●字节对齐是一种典型,以空间换时间的策略的,在现代计算机拥有较大的内存的情况,这个策略是相当成功的. 为什么要字节对齐? ●加快程序访问速度 ●很多CPU对访问地址有严格要求,这时编译器必须要这个CPU的规范来实现,X86较为宽松,不对齐结构可能只 影响效率,如ARM,访问地址必须基于偶地址,MIPS和Sparc也类似,这样不对齐的地址访问会造成错误. 关于字节对齐的实现 在不同的CPU对地址对齐有不同要求,各个编译器也会采用不同策略来实现字节对齐,在随后的例子,可以对比PC下的Windows,和Linux,以有ARM下的字节对齐策略.

c++中关于结构体长度的计算问题

[C++]字节对齐与结构体大小 [C++] 2010-09-24 21:40:26 阅读172 评论0 字号:大中小订阅 说明: 结构体的sizeof值,并不是简单的将其中各元素所占字节相加,而是要考虑到存储空间的字节对齐问题。这些问题在平时编程的时候也确实不怎么用到,但在一些笔试面试题目中出是常常出现,对sizeof我们将在另一篇文章中总结,这篇文章我们只总结结构体的sizeof,报着不到黄河心不死的决心,终于完成了总结,也算是小有收获,拿出来于大家分享,如果有什么错误或者没有理解透的地方还望能得到提点,也不至于误导他人。 一、解释 现代计算机中内存空间都是按照byte划分的,从理论上讲似乎对任何类型的变量的访问可以从任何地址开始,但实际情况是在访问特定类型变量的时候经常在特定的内存地址访问,这就需要各种类型数据按照一定的规则在空间上排列,而不是顺序的一个接一个的排放,这就是对齐。 各个硬件平台对存储空间的处理上有很大的不同。一些平台对某些特定类型的数据只能从某些特定地址开始存取。比如

有些架构的CPU在访问一个没有进行对齐的变量的时候会发生错误,那么在这种架构下编程必须保证字节对齐.其他平台可能没有这种情况,但是最常见的是如果不按照适合其平台要求对数据存放进行对齐,会在存取效率上带来损失。比如有些平台每次读都是从偶地址开始,如果一个int 型(假设为32位系统)如果存放在偶地址开始的地方,那么一个读周期就可以读出这32bit,而如果存放在奇地址开始的地方,就需要2个读周期,并对两次读出的结果的高低字节进行拼凑才能得到该32bit数据。 二、准则 其实字节对齐的细节和具体编译器实现相关,但一般而言,满足三个准则: 1. 结构体变量的首地址能够被其最宽基本类型成员的大小所整除; 2. 结构体每个成员相对于结构体首地址的偏移量都是成员大小的整数倍,如有需要编译器会在成员之间加上填充字节; 3. 结构体的总大小为结构体最宽基本类型成员大小的整数倍,如有需要编译器会在最末一个成员之后加上填充字节。 三、基本概念

H264详解

1.引言 H.264的主要目标: 1.高的视频压缩比 2.良好的网络亲和性 解决方案: VCL video coding layer 视频编码层 NAL network abstraction layer 网络提取层 VCL:核心算法引擎,块,宏块及片的语法级别的定义 NAL:片级以上的语法级别(如序列参数集和图像参数集),同时支持以下功能:独立片解码,起始码唯一保证,SEI以及流格式编码数据传送 VCL设计目标:尽可能地独立于网络的情况下进行高效的编解码 NAL设计目标:根据不同的网络把数据打包成相应的格式,将VCL产生的比特字符串适配到各种各样的网络和多元环境中。 NALU头结构:NALU类型(5bit)、重要性指示位(2bit)、禁止位(1bit)。 NALU类型:1~12由H.264使用,24~31由H.264以外的应用使用。 重要性指示:标志该NAL单元用于重建时的重要性,值越大,越重要。 禁止位:网络发现NAL单元有比特错误时可设置该比特为1,以便接收方丢掉该单元。 2.NAL语法语义 NAL层句法: 在编码器输出的码流中,数据的基本单元是句法元素。 句法表征句法元素的组织结构。 语义阐述句法元素的具体含义。 分组都有头部,解码器可以很方便的检测出NAL的分界,依次取出NAL进行解码。 但为了节省码流,H.264没有另外在NAL的头部设立表示起始位置的句法元素。

如果编码数据是存储在介质上的,由于NAL是依次紧密相连的,解码器就无法在数据流中分辨出每个NAL的起始位置和终止位置。 解决方案:在每个NAL前添加起始码:0X000001 在某些类型的介质上,为了寻址的方便,要求数据流在长度上对齐,或某个常数的整数倍。所以在起始码前添加若干字节的0来填充。 检测NAL的开始: 0X000001和0X000000 我们必须考虑当NAL内部出现了0X000001和0X000000 解决方案: H.264提出了“防止竞争”机制: 0X000000——0X00000300 0X000001——0X00000301 0X000002——0X00000302 0X000003——0X00000303 为此,我们可以知道: 在NAL单元中,下面的三字节序列不应在任何字节对齐的位置出现 0X000000 0X000001 0X000002 Forbidden_zero_bit =0; Nal_ref_idc:表示NAL的优先级。0~3,取值越大,表示当前NAL越重要,需要优先受到保护。如果当前NAL是属于参考帧的片,或是序列参数集,或是图像参数集这些重要的单位时,本句法元素必需大于0。 Nal_unit_type:当前NAL 单元的类型 3.H.264的NAL层处理

程序设计与问题求解下实验答案

实验数组、结构体和函数综合编程练习 1.学生成绩统计 从键盘输入一个班(全班最多不超过30 人)学生某门课的成绩,当输入成绩为 负值时,输入结束,分别实现下列功能: (1)统计不及格人数并打印不及格学生名单; (2)统计成绩在全班平均分及平均分之上的学生人数,并打印这些学生的名单;(3)统计各分数段的学生人数及所占的百分比。 注:将成绩分为六个分数段,60 分以下为第0 段,60~69 为第1 段,70~79 为第2 段,80~89 为第3 段,90~99为第4 段,100 分为第5 段。 编程要求: 1. 较好的用户输入输出提示信息 2. 使用子函数来实现上述各个功能,并且要使用结构体数组来实现,该结构体中包括学生学号和成绩 3. 最好不要使用全局变量 #include #define ARR_SIZE 30 typedef struct tagStudent { long num;//学生学号 float score;//学生分数 }Student; int ReadScore(Student stu[]); int GetFail(Student stu[], int n); float GetAver(Student stu[], int n); int GetAboveAver(Student stu[], int n); void GetDetail(Student stu[], int n); main()

{ int n, fail, aboveAver; Student stu[ARR_SIZE]; printf("Please enter num and score until score<0:\n"); n = ReadScore(stu); printf("Total students:%d\n", n); fail = GetFail(stu, n); printf("Fail students = %d\n",fail); aboveAver = GetAboveAver(stu, n); printf("Above aver students = %d\n", aboveAver); GetDetail(stu, n); } /* 函数功能:从键盘输入一个班学生某门课的成绩及其学号当输入成绩为负值时,输入结束 函数参数:存放学生信息的Student 结构体数组 函数返回值:学生总数 */ int ReadScore(Student stu[]) { int i = 0; scanf("%ld%f", &stu[i].num, &stu[i].score); while (stu[i].score >= 0) { i++; scanf("%ld%f", &stu[i].num, &stu[i].score); } return i; } /* 函数功能:统计不及格人数并打印不及格学生名单 函数参数:存放学生信息的Student 结构体数组 整型变量n,存放学生总数 函数返回值:不及格人数 */ int GetFail(Student stu[], int n) { int i, count;

stm32中使用#pragma pack(非常有用的字节对齐用法说明)

#pragma pack(4) //按4字节对齐,但实际上由于结构体中单个成员的最大占用字节数为2字节,因此实际还是按2字节对齐 typedef struct { char buf[3];//buf[1]按1字节对齐,buf[2]按1字节对齐,由于buf[3]的下一成员word a是按两字节对齐,因此buf[3]按1字节对齐后,后面只需补一空字节 word a; //#pragma pack(4),取小值为2,按2字节对齐。 }kk; #pragma pack() //取消自定义字节对齐方式 对齐的原则是min(sizeof(word ),4)=2,因此是2字节对齐,而不是我们认为的4字节对齐。 这里有三点很重要: 1.每个成员分别按自己的方式对齐,并能最小化长度 2.复杂类型(如结构)的默认对齐方式是它最长的成员的对齐方式,这样在成员是复杂类型时,可以最小化长度 3.对齐后的结构体整体长度必须是成员中最大的对齐参数的整数倍,这样在处理数组时可以保证每一项都边界对齐 补充一下,对于数组,比如: char a[3];这种,它的对齐方式和分别写3个char是一样的.也就是说它还是按1个字节对齐. 如果写: typedef char Array3[3]; Array3这种类型的对齐方式还是按1个字节对齐,而不是按它的长度. 不论类型是什么,对齐的边界一定是1,2,4,8,16,32,64....中的一个. 声明: 整理自网络达人们的帖子,部分参照MSDN。 作用: 指定结构体、联合以及类成员的packing alignment; 语法: #pragma pack( [show] | [push | pop] [, identifier], n ) 说明: 1,pack提供数据声明级别的控制,对定义不起作用; 2,调用pack时不指定参数,n将被设成默认值; 3,一旦改变数据类型的alignment,直接效果就是占用memory的减少,但是performance会下降; 语法具体分析: 1,show:可选参数;显示当前packing aligment的字节数,以warning message的形式被显示; 2,push:可选参数;将当前指定的packing alignment数值进行压栈操作,这里的栈是the internal compiler stack,同时设置当前的packing alignment为n;如果n没有指定,则将当前的packing alignment数值压栈; 3,pop:可选参数;从internal compiler stack中删除最顶端的record;如果没有指定n,则当前栈顶record即为新的packing alignment 数值;如果指定了n,则n将成为新的packing aligment数值;如果指定了identifier,则internal compiler stack中的record都将被pop 直到identifier被找到,然后pop出identitier,同时设置packing alignment数值为当前栈顶的record;如果指定的identifier并不存在于internal compiler stack,则pop操作被忽略; 4,identifier:可选参数;当同push一起使用时,赋予当前被压入栈中的record一个名称;当同pop一起使用时,从internal compiler stack 中pop出所有的record直到identifier被pop出,如果identifier没有被找到,则忽略pop操作; 5,n:可选参数;指定packing的数值,以字节为单位;缺省数值是8,合法的数值分别是1、2、4、8、16。 重要规则: 1,复杂类型中各个成员按照它们被声明的顺序在内存中顺序存储,第一个成员的地址和整个类型的地址相同; 2,每个成员分别对齐,即每个成员按自己的方式对齐,并最小化长度;规则就是每个成员按其类型的对齐参数(通常是这个类型的大小)和指定对齐参数中较小的一个对齐; 3,结构体、联合体或者类的数据成员,第一个放在偏移为0的地方;以后每个数据成员的对齐,按照#pragma pack指定的数值和这个数据成员自身长度两个中比较小的那个进行;也就是说,当#pragma pack指定的值等于或者超过所有数据成员长度的时候,这个指定值的大小将不产生任何效果; 4,复杂类型(如结构体)整体的对齐是按照结构体中长度最大的数据成员和#pragma pack指定值之间较小的那个值进行;这样当数据成员为复杂类型(如结构体)时,可以最小化长度; 5,复杂类型(如结构体)整体长度的计算必须取所用过的所有对齐参数的整数倍,不够补空字节;也就是取所用过的所有对齐参数中最大的那个值的整数倍,因为对齐参数都是2的n次方;这样在处理数组时可以保证每一项都边界对齐; 对齐的算法:由于各个平台和编译器的不同,现以本人使用的gcc version 3.2.2编译器(32位x86平台)为例子,来讨论编译器对struct 数据结构中的各成员如何进行对齐的。 在相同的对齐方式下,结构体内部数据定义的顺序不同,结构体整体占据内存空间也不同,如下: 设结构体如下定义: struct A { int a; //a的自身对齐值为4,偏移地址为0x00~0x03,a的起始地址0x00满足0x00%4=0;

dm9000中文芯片手册

DM9000介绍 1、总体介绍 该DM9000是一款完全集成的和符合成本效益单芯片快速以太网MAC控制器与一般处理接口,一个10/100M自适应的PHY和4K DWORD值的SRAM 。它的目的是在低功耗和高性能进程的3.3V与5V的支持宽容。 DM9000还提供了介质无关的接口,来连接所有提供支持介质无关接口功能的家用电话线网络设备或其他收发器。该DM9000支持8位,16位和32 -位接口访问内部存储器,以支持不同的处理器。DM9000物理协议层接口完全支持使用10MBps下3类、4类、5类非屏蔽双绞线和100MBps下5类非屏蔽双绞线。这是完全符合IEEE 8 02.3u规格。它的自动协调功能将自动完成配置以最大限度地适合其线路带宽。还支持IEEE 802.3x全双工流量控制。这个工作里面DM9000是非常简单的,所以用户可以容易的移植任何系统下的端口驱动程序。 2、特点 支持处理器读写内部存储器的数据操作命令以字节/ 字/ 双字的长度进行 集成10/100M自适应收发器 支持介质无关接口 支持背压模式半双工流量控制模式 IEEE802.3x流量控制的全双工模式 支持唤醒帧,链路状态改变和远程的唤醒 4K双字SRAM 支持自动加载EEPROM里面生产商ID和产品ID 支持4个通用输入输出口 超低功耗模式 功率降低模式 电源故障模式 可选择1:1或5:4变压比例的变压器降低格外功率 兼容3.3v和5.0v输入输出电压 100脚CMOS LQFP封装工艺 3、引脚描述 I=输入O=输出I/O=输入/输出O/D=漏极开路P=电源LI=复位锁存输入#=普遍低电位 介质无关接口引脚 引脚号引脚名I/O 功能描述 37 LINK_I I 外部介质无关接口器件连接

实验8结构体应用

实验8。结构体应用—-—10081 学生成绩统计(结构体) 1。【问题描述】用结构数组实现学生成绩统计各功能。?某班有N(N<=30)个学生,共开设5门课程,分别用三个函数实现如下功能:?⑴求第一门课程得平均分; ⑵找出有2门及2门以上不及格得学生,并输出其学号; ⑶找出平均成绩在90分及以上得学生,输出她们得学号。 【输入形式】第一行为一个整数N,表示本班共N个人,接下来得N行中每行包含一个学生得信息,包括学号(长度 小于11得字符串)、课程1成绩、课程2成绩、课程3成绩、课程4成绩、课程5成绩。成绩均为整数。?【输出形式】输出共三行:?第一行为本班第一门课程得平均成绩。(保留小数点后两位) 第二行为有2门及2门以上不及格得学生得学号,各学号之间用一个空格分隔。若不存在,则打印”no”、 第三行为平均成绩在90分及以上得学生得学号, 各学号之间用一个空格分隔、若不存在,则打印"no"。 ?【样例输入】3 070002 93 95 90 88 92 0700019080 85 50 42? 070003 9892 84 90 91 【样例输出】93、67 070001?070002070003?【样例说明】本班有3个学生。?第1个学生学号为: 070001, 5门课程得成绩分别为: 90、80、85、50、42;?第2个学生学号为: 070002, 5门课程得成绩分别为:93、95、90、88、92;?第3个学生学号为: 070003, 5门课程得成绩分别为: 98、92、84、90、91。 本班第1门课程得平均成绩为: 93。67;有2门及2门以上不及格得学生得学号为: 070001; 平均成绩在90分及以上得学生得学号为: 070002、070003。?【评分标准】本题共2个测试点,每个测试点1、0分,共2。0分。 #include <stdio.h> structstudent { char num[10]; intb[5]; }a[30]; int main() { voideverage(structstudent*p1,int m); voidfailure(struct student*p2,int m); void success(struct student*p3,int m); int n,i; scanf(”%d",&n); for(i=0;i<n;i++) scanf("%s%d%d%d%d%d”,a[i]、num,&a[i]、b[0],&a[i]。b[1],&a[i]。b[2],&a[i]。b[3],&a[i]。b[4]); everage(a,n); failure(a,n); success(a,n); return0; } voideverage(structstudent*p1,int m) {

C语言内存字节对齐规则20180718

C语言内存字节对齐规则 在C语言面试和考试中经常会遇到内存字节对齐的问题。今天就来对字节对齐的知识进行小结一下。 首先说说为什么要对齐。为了提高效率,计算机从内存中取数据是按照一个固定长度的。以32位机为例,它每次取32个位,也就是4个字节(每字节8个位,计算机基础知识,别说不知道)。字节对齐有什么好处?以int型数据为例,如果它在内存中存放的位置按4字节对齐,也就是说1个int的数据全部落在计算机一次取数的区间内,那么只需要取一次就可以了。如图a-1。如果不对齐,很不巧,这个int数据刚好跨越了取数的边界,这样就需要取两次才能把这个int的数据全部取到,这样效率也就降低了。 图:a-1 图:a-2 内存对齐是会浪费一些空间的。但是这种空间上得浪费却可以减少取数的时间。这是典型的一种以空间换时间的做法。空间与时间孰优孰略这个每个人都有自己的看法,但是C 语言既然采取了这种以空间换时间的策略,就必然有它的道理。况且,在存储器越来越便宜的今天,这一点点的空间上的浪费就不算什么了。 需要说明的是,字节对齐不同的编译器可能会采用不同的优化策略,以下以GCC为例讲解结构体的对齐. 一、原则: 1.结构体内成员按自身按自身长度自对齐。

自身长度,如char=1,short=2,int=4,double=8,。所谓自对齐,指的是该成员的起始位置的内存地址必须是它自身长度的整数倍。如int只能以0,4,8这类的地址开始 2.结构体的总大小为结构体的有效对齐值的整数倍 结构体的有效对齐值的确定: 1)当未明确指定时,以结构体中最长的成员的长度为其有效值 2)当用#pragma pack(n)指定时,以n和结构体中最长的成员的长度中较小者为其值。 3)当用__attribute__ ((__packed__))指定长度时,强制按照此值为结构体的有效对齐值 二、例子 1) struct AA{ //结构体的有效对齐值为其中最大的成员即int的长度4 char a; int b; char c; }aa 结果,sizeof(aa)=12 何解?首先假设结构体内存起始地址为0,那么地址的分布如下 0 a 1 2 3 4 b 5 b 6 b 7 b 8 c 9 10 11 char的字对齐长度为1,所以可以在任何地址开始,但是,int自对齐长度为4,必须以4的倍数地址开始。所以,尽管1-3空着,但b也只能从4开始。再加上c后,整个结构体的总长度为9,结构体的有效对齐值为其中最大的成员即int的长度4,所以,结构体的大小向上扩展到12,即9-11的地址空着。 2) //结构体的有效对齐值为其中最大的成员即int的长度4 struct AA{ char a; char c; int b; }aa sizeof(aa)=8,为什么呢 0 a 1 c

C语言结构体对齐问题

C语言结构体对齐问题 1。几个结构体例子: struct{ short a1; short a2; short a3; }A; struct{ long a1; short a2; }B; sizeof( A)=6, sizeof( B)=8,为什么? 注:sizeof(short)=2,sizeof(long)=4 因为:“成员对齐有一个重要的条件,即每个成员按自己的方式对齐。其对齐的规则是,每个成员按其类型的对齐参数(通常是这个类型的大小)和指定对齐参数(这里默认是8字节)中较小的一个对齐。并且结构的长度必须为所用过的所有对齐参数的整数倍,不够就补空字节。”(引用) 结构体A中有3个short类型变量,各自以2字节对齐,结构体对齐参数按默认的8字节对齐,则a1,a2,a3都取2字节对齐,则sizeof(A)为6,其也是2的整数倍; B中a1为4字节对齐,a2为2字节对齐,结构体默认对齐参数为8,则a1取4字节对齐,a2取2字节对齐,结构体大小6字节,6不为4的整数倍,补空字节,增到8时,符合所有条件,则sizeof(B)为8; 可以设置成对齐的 #pragma pack(1) #pragma pack(push) #pragma pack(1) struct{

short a1; short a2; short a3; }A; struct{ long a1; short a2; }B; #pragma pack(pop) 结果为sizeof( A)=6,sizeof( B)=6 ************************ #pragma pack(8) struct S1{ char a; long b; }; struct S2 { char c; struct S1 d; long long e; }; #pragma pack() sizeof(S2)结果为24.

C语言字节对齐

定的规则在空间上排列,而不是顺序的一个接一个的排放,这就是对齐 对齐的作用和原因:各个硬件平台对存储空间的处理上有很大的不同一些平台对某些特定类型的数据只能从某些特定地址开始存取其他平台可能没有这种情况, 台的要求对数据存放进行对齐,会在存取效率上带来损失比如有些平台每次读都是从偶地址开始,如果数据显然在读取效率上下降很多这也是空间和时间的博弈 不需要考虑对齐问题编译器会替我们选择适合目标平台的对齐策略当然,我们也可以通知给编译器传递预编译指令而改变对指定数据的对齐方法 话,常常会对一些问题感到迷惑最常见的就是结果,出乎意料为此,我们需要对对齐算法所了解 数据结构中的各成员如何进行对齐的 型数据一个 字节但是因为编译器要对数据成员在空间上进行对齐现在把该结构体调整成员变量的顺序

8 7 ,单位字节 )数据类型自身的对齐值:就是上面交代的基本数据类型的自身对齐值 )结构体或者类的自身对齐值:其成员中自身对齐值最大的那个值 )数据成员、结构体和类的有效对齐值:自身对齐值和指定对齐值中较小的那个值 有了这些值,我们就可以很方便的来讨论具体数据结构的成员和其自身的对齐方式有效对齐值 最终用来决定数据存放地址方式的值,最重要有效对齐 而数据结构中的数据变量都是按定义的先后顺序来排放的第一个数据变量的数据结构的起始地址结构体的成员变量要对齐排放,结构体本身也要根据自身的有效对 结合下面例子理解)这样就不难理解上面的几个例子的值了

开始排放该例子中没有定义指定对齐值,在笔者环境下,该值默认为 第三个变量 内容再看数据结构 所占用故 的变量又 的八个字节所以

#pragma指令用法汇总和解析

#pragma指令用法汇总和解析 一. message 参数。 message 它能够在编译信息输出窗 口中输出相应的信息,这对于源代码信息的控制是非常重要的。其使用方法为: #pragma message(“消息文本”) 当编译器遇到这条指令时就在编译输出窗口中将消息文本打印出来。 当我们在程序中定义了许多宏来控制源代码版本的时候,我们自己有可能都会忘记有没有正确的设置这些宏,此时我们可以用这条 指令在编译的时候就进行检查。假设我们希望判断自己有没有在源代码的什么地方定义了_X86这个宏可以用下面的方法 #ifdef _X86 #pragma message(“_X86 macro activated!”) #endif 当我们定义了_X86这个宏以后,应用程序在编译时就会在编译输出窗口里显示“_ X86 macro activated!”。我们就不会因为不记得自己定义的一些特定的宏而抓耳挠腮了 二. 另一个使用得比较多的#pragma参数是code_seg。格式如: #pragma code_seg( [ [ { push | pop}, ] [ identifier, ] ] [ "segment-name" [, "segment-class" ] ) 该指令用来指定函数在.obj文件中存放的节,观察OBJ文件可以使用VC自带的dumpbin命令行程序,函数在.obj文件中默认的存放节 为.text节 如果code_seg没有带参数的话,则函数存放在.text节中 push (可选参数) 将一个记录放到内部编译器的堆栈中,可选参数可以为一个标识符或者节名 pop(可选参数) 将一个记录从堆栈顶端弹出,该记录可以为一个标识符或者节名 identifier (可选参数) 当使用push指令时,为压入堆栈的记录指派的一个标识符,当该标识符被删除的时候和其相关的堆栈中的记录将被弹出堆栈 "segment-name" (可选参数) 表示函数存放的节名 例如: //默认情况下,函数被存放在.text节中 void func1() { // stored in .text } //将函数存放在.my_data1节中 #pragma code_seg(".my_data1") void func2() { // stored in my_data1 } //r1为标识符,将函数放入.my_data2节中 #pragma code_seg(push, r1, ".my_data2") void func3() { // stored in my_data2 } int main() { } 三. #pragma once (比较常用) 这是一个比较常用的指令,只要在头文件的最开始加入这条指令就能够保证头文件被编译一次 四. #pragma hdrstop表示预编译头文件到此为止,后面的头文件不进行预编译。

字节序(byte order)和位序(bit order)

字节序(byte order)和位序(bit order) 字节序(byte order)和位序(bit order) 在网络编程中经常会提到网络字节序和主机序,也就是说当一个对象由多个字节组成的时候需要注意对象的多个字节 在内存中的顺序。 以前我也基本只了解过字节序,但是有一天当我看到ip.h中对IP头部结构体struct iphdr的定义时,我发现其中竟然对一个字节中的8个比特位也区分了大小端,这时我就迷糊了,不是说大小端只有在多个字节之间才会有区分的吗,为什么这里的定义却对一个字节中的比特位也区分大小端呢? 下面我们先看一下struct iphdr的定义,后文会解惑为什么要在一个字节中区分大小端。 struct iphdr { #if defined(__LITTLE_ENDIAN_BITFIELD) __u8 ihl:4, version:4; #elif defined (__BIG_ENDIAN_BITFIELD) __u8 version:4, ihl:4;

#else #error "Please fix " #endif __u8 tos; __be16 tot_len; __be16 id; __be16 frag_off; __u8 ttl; __u8 protocol; __sum16 check; __be32 saddr; __be32 daddr; /*The options start here. */ }; 字节序(Byte order) 关于字节序的文章已经有很多了,在我这篇文章中不打算过多的说字节序,但是也不能完全脱离字节序因为后面的重点部分比特序跟字节序也有一定的相似度和联系。 字节序就是说一个对象的多个字节在内存中如何排序存放,

相关文档
最新文档