C语言内存对齐

C语言内存对齐
C语言内存对齐

解析C语言结构体对齐(内存对齐问题)

C语言结构体对齐也是老生常谈的话题了。基本上是面试题的必考题。内容虽然很基础,但一不小心就会弄错。写出一个struct,然后sizeof,你会不会经常对结果感到奇怪?sizeof的结果往往都比你声明的变量总长度要大,这是怎么回事呢?

开始学的时候,也被此类问题困扰很久。其实相关的文章很多,感觉说清楚的不多。结构体到底怎样对齐?

有人给对齐原则做过总结,具体在哪里看到现在已记不起来,这里引用一下前人的经验(在没有#pragma pack宏的情况下):

原则1、数据成员对齐规则:结构(struct或联合union)的数据成员,第一个数据成员放在offset为0的地方,以后每个数据成员存储的起始位置要从该成员大小的整数倍开始(比如int在32位机为4字节,则要从4的整数倍地址开始存储)。

原则2、结构体作为成员:如果一个结构里有某些结构体成员,则结构体成员要从其内部最大元素大小的整数倍地址开始存储。(struct a里存有struct b,b里有char,int,double等元素,那b应该从8的整数倍开始存储。)

原则3、收尾工作:结构体的总大小,也就是sizeof的结果,必须是其内部最大成员的整数倍,不足的要补齐。

这三个原则具体怎样理解呢?我们看下面几个例子,通过实例来加深理解。

例1:struct {

short a1;

short a2;

short a3;

}A;

struct{

long a1;

short a2;

}B;

sizeof(A) = 6; 这个很好理解,三个short都为2。

sizeof(B) = 8; 这个比是不是比预想的大2个字节?long为4,short为2,整个为8,因为原则3。

例2:struct A{

int a;

char b;

short c;

};

struct B{

char b;

int a;

short c;

};

sizeof(A) = 8; int为4,char为1,short为2,这里用到了原则1和原则3。

sizeof(B) = 12; 是否超出预想范围?char为1,int为4,short为2,怎么会是12?还是原则1和原则3。

深究一下,为什么是这样,我们可以看看内存里的布局情况。

a b c

A的内存布局:1111, 1*, 11

b a c

B的内存布局:1***, 1111, 11**

其中星号*表示填充的字节。A中,b后面为何要补充一个字节?因为c为short,其起始位置要为2的倍数,就是原则1。c的后面没有补充,因为b和c正好占用4个字节,整个A占用空间为4的倍数,也就是最大成员int类型的倍数,所以不用补充。

B中,b是char为1,b后面补充了3个字节,因为a是int为4,根据原则1,起始位置要为4的倍数,所以b后面要补充3个字节。c后面补充两个字节,根据原则3,整个B占用空间要为4的倍数,c后面不补充,整个B的空间为10,不符,所以要补充2个字节。再看一个结构中含有结构成员的例子:

例3:struct A{

int a;

double b;

float c;

};

struct B{

char e[2];

int f;

double g;

short h;

struct A i;

};

sizeof(A) = 24; 这个比较好理解,int为4,double为8,float为4,总长为8的倍数,补齐,所以整个A为24。

sizeof(B) = 48; 看看B的内存布局。

e f g h i

B的内存布局:11* *, 1111, 11111111, 11 * * * * * *, 1111* * * *, 11111111, 1111 * * * *

i其实就是A的内存布局。i的起始位置要为24的倍数,所以h后面要补齐。把B的内存布局弄清楚,有关结构体的对齐方式基本就算掌握了。

以上讲的都是没有#pragma pack宏的情况,如果有#pragma pack宏,对齐方式按照宏的定义来。比如上面的结构体前加#pragma pack(1),内存的布局就会完全改变。sizeof(A) = 16; sizeof(B) = 32;

有了#pragma pack(1),内存不会再遵循原则1和原则3了,按1字节对齐。没错,这不是理想中的没有内存对齐的世界吗。

a b c

A的内存布局:1111, 11111111, 1111

e f g h i

B的内存布局:11, 1111, 11111111, 11 , 1111, 11111111, 1111

那#pragma pack(2)的结果又是多少呢?#pragma pack(4)呢?留给大家自己思考吧,相信没有问题。

还有一种常见的情况,结构体中含位域字段。位域成员不能单独被取sizeof值。C99规

定int、unsigned int和bool可以作为位域类型,但编译器几乎都对此作了扩展,允许其它类型类型的存在。

使用位域的主要目的是压缩存储,其大致规则为:

1) 如果相邻位域字段的类型相同,且其位宽之和小于类型的sizeof大小,则后面的字段将紧邻前一个字段存储,直到不能容纳为止;

2) 如果相邻位域字段的类型相同,但其位宽之和大于类型的sizeof大小,则后面的字段将从新的存储单元开始,其偏移量为其类型大小的整数倍;

3) 如果相邻的位域字段的类型不同,则各编译器的具体实现有差异,VC6采取不压缩方式,Dev-C++采取压缩方式;

4) 如果位域字段之间穿插着非位域字段,则不进行压缩;

5) 整个结构体的总大小为最宽基本类型成员大小的整数倍。

还是让我们来看看例子。

例4:struct A{

char f1 : 3;

char f2 : 4;

char f3 : 5;

};

a b c

A的内存布局:111, 1111 *, 11111 * * *

位域类型为char,第1个字节仅能容纳下f1和f2,所以f2被压缩到第1个字节中,而f3只能从下一个字节开始。因此sizeof(A)的结果为2。

例5:struct B{

char f1 : 3;

short f2 : 4;

char f3 : 5;

};

由于相邻位域类型不同,在VC6中其sizeof为6,在Dev-C++中为2。

例6:struct C{

char f1 : 3;

char f2;

char f3 : 5;

};

非位域字段穿插在其中,不会产生压缩,在VC6和Dev-C++中得到的大小均为3。

考虑一个问题,为什么要设计内存对齐的处理方式呢?如果体系结构是不对齐的,成员将会一个挨一个存储,显然对齐更浪费了空间。那么为什么要使用对齐呢?体系结构的对齐和不对齐,是在时间和空间上的一个权衡。对齐节省了时间。假设一个体系结构的字长为w,那么它同时就假设了在这种体系结构上对宽度为w的数据的处理最频繁也是最重要的。它的设计也是从优先提高对w位数据操作的效率来考虑的。有兴趣的可以google一下,人家就可以跟你解释的,一大堆的道理。

最后顺便提一点,在设计结构体的时候,一般会尊照一个习惯,就是把占用空间小的类型排在前面,占用空间大的类型排在后面,这样可以相对节约一些对齐空间。

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 */

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,

C语言的代码内存布局具体解释

一个程序本质上都是由BSS 段、data段、text段三个组成的。这种概念在当前的计算机程序设计中是非常重要的一个基本概念,并且在嵌入式系统的设计中也非常重要,牵涉到嵌入式系统执行时的内存大小分配,存储单元占用空间大小的问题。 ●BSS段:在採用段式内存管理的架构中。BSS段(bss segment)一般是指用 来存放程序中未初始化的全局变量的一块内存区域。 BSS是英文Block Started by Symbol的简称。 BSS段属于静态内存分配。 ●数据段:在採用段式内存管理的架构中,数据段(data segment)一般是指 用来存放程序中已初始化的全局变量的一块内存区域。数据段属于静态内存分配。 ●代码段:在採用段式内存管理的架构中,代码段(text segment)一般是指 用来存放程序执行代码的一块内存区域。这部分区域的大小在程序执行前就已经确定,而且内存区域属于仅仅读。 在代码段中。也有可能包括一些仅仅读的常数变量,比如字符串常量等。 程序编译后生成的目标文件至少含有这三个段。这三个段的大致结构图例如以下所看到的: 当中.text即为代码段,为仅仅读。.bss段包括程序中未初始化的全局变量和static变量。 data段包括三个部分:heap(堆)、stack(栈)和静态数据区。 ●堆(heap):堆是用于存放进程执行中被动态分配的内存段。它的大小并不 固定,可动态扩张或缩减。当进程调用malloc等函数分配内存时。新分配的内存就被动态加入到堆上(堆被扩张);当利用free等函数释放内存时,被释放的内存从堆中被剔除(堆被缩减)

栈(stack):栈又称堆栈,是用户存放程序暂时创建的局部变量,也就是说我们函数括弧“{}”中定义的变量(但不包含static声明的变量。static意味着在数据段中存放变量)。 除此以外,在函数被调用时。其參数也会被压入发起调用的进程栈中。而且待到调用结束后。函数的返回值也会被存放回栈中。 因为栈的先进先出特点,所以栈特别方便用来保存/恢复调用现场。从这个意义上讲,我们能够把堆栈看成一个寄存、交换暂时数据的内存区。 当程序在运行时动态分配空间(C中的malloc函数),所分配的空间就属于heap。其概念与数据结构中“堆”的概念不同。 stack段存放函数内部的变量、參数和返回地址,其在函数被调用时自己主动分配。訪问方式就是标准栈中的LIFO方式。 (由于函数的局部变量存放在此,因此其訪问方式应该是栈指针加偏移的方式,否则若通过push、pop操作来訪问相当麻烦) data段中的静态数据区存放的是程序中已初始化的全局变量、静态变量和常量。 在採用段式内存管理的架构中(比方intel的80x86系统),BSS 段(Block Started by Symbol segment)一般是指用来存放程序中未初始化的全局变量的一块内存区域,一般在初始化时BSS 段部分将会清零。BSS 段属于静态内存分配。即程序一開始就将其清零了。 比方,在C语言之类的程序编译完毕之后,已初始化的全局变量保存在.data 段中,未初始化的全局变量保存在.bss 段中。 text和data段都在可运行文件里(在嵌入式系统里通常是固化在镜像文件里)。由系统从可运行文件里载入;而BSS段不在可运行文件里,由系统初始化。

内存字节对齐

1.内存字节对齐和小端模式: /* 本程序是关于:编译器内存的字节对齐方式和存储时的小端对齐模式(win7 32bit) #pragma pack(n) 默认为8字节对齐,(即n=8)其中n的取值为1,2,4,8,16,32等 内存字节对齐大小和方式: 1)结构体内变量对齐: 每个变量的对齐字节数大小argAlignsize=min(#pragma pack(n),sizeof(变量)); 方式:结构体的第一个变量的初始偏移地址为0,其它变量的偏移地址(当前变量的起始地址)必须是argAlignsize的整数倍,不够整数倍的补空,不添加任何数据 2)结构体对齐: 结构体的对齐字节数大小strAlignsize=min(#pragma pack(n),sizeof(所有变量中最大字节的变量)) 方式: A.对于单独的结构体来说,结构体本身按照strAlignsize大小来对齐 B.结构体B在结构体A中时,结构体B的起始地址是结构体B的 strAlignsize大小的整数倍 小端对齐模式: 指针指着一个存储空间,存储空间地址由低到高的存储内容为:0x78,0x67,0x33,0x45 若指针为char,则获取的数据为0x78 若指针为short,则获取的数据为0x6778 若指针为long,则获取的数据为0x45336778 */ #include using namespace std; /*更改C编译器内存的缺省字节对齐方式,由默认的n=4字节,变为n字节对齐,其中n的取值为1,2,4,8,16,32等*/ #pragma pack(2) struct A { unsigned char a; unsigned short b; }; struct B { unsigned char c; unsigned int d;

寄存器sse2指令集

sse2指令集 1移动指令: 1. Movaps movaps XMM,XMM/m128 movaps XMM/128,XMM 把源存储器内容值送入目的寄存器,当有m128时,必须对齐内存16字节,也就是内存地址低4位为0. 2. Movups movups XMM,XMM/m128 movaps XMM/128,XMM 把源存储器内容值送入目的寄存器,但不必对齐内存16字节 3. Movlps movlps XMM,m64 把源存储器64位内容送入目的寄存器低64位,高64位不变,内存变量不必对齐内存16字节4. Movhps movhps XMM,m64 把源存储器64位内容送入目的寄存器高64位,低64位不变,内存变量不必对齐内存16字节. 5. Movhlps movhlps XMM,XMM 把源寄存器高64位送入目的寄存器低64位,高64位不变. 6. Movlhps movlhps XMM,XMM 把源寄存器低64位送入目的寄存器高64位,低64位不变. 7. movss movss XMM,m32/XMM 原操作数为m32时:dest[31-00] <== m32 dest[127-32] <== 0 原操作数为XMM时: dest[31-00] <== src[31-00] dest[127-32]不变 8. movmskpd movmskpd r32,XMM 取64位操作数符号位 r32[0] <== XMM[63] r32[1] <== XMM[127] r32[31-2] <== 0

9. movmskps movmskps r32,XMM 取32位操作数符号位 r32[0] <== XMM[31] r32[1] <== XMM[63] r32[2] <== XMM[95] r32[3] <== XMM[127] r32[31-4] <== 0 10. pmovmskb pmovmskb r32,XMM 取16位操作数符号位具体操作同前 r[0] <== XMM[7] r[1] <== XMM[15] r[2] <== XMM[23] r[3] <== XMM[31] r[4] <== XMM[39] r[5] <== XMM[47] r[6] <== XMM[55] r[7] <== XMM[63] r[8] <== XMM[71] r[9] <== XMM[79] r[10] <== XMM[87] r[11] <== XMM[95] r[12] <== XMM[103] r[13] <== XMM[111] r[14] <== XMM[119] r[15] <== XMM[127] r[31-16] <== 0 11. movntps movntps m128,XMM m128 <== XMM 直接把XMM中的值送入m128,不经过cache,必须对齐16字节. 12. Movntpd movntpd m128,XMM m128 <== XMM 直接把XMM中的值送入m128,不经过cache,必须对齐16字节. 13. Movnti movnti m32,r32 m32 <== r32 把32寄存器的值送入m32,不经过cache. 14. Movapd movapd XMM,XMM/m128 movapd XMM/m128,XMM 把源存储器内容值送入目的寄存器,当有m128时,必须对齐内存16字节 15. Movupd movupd XMM,XMM/m128 movapd XMM/m128,XMM 把源存储器内容值送入目的寄存器,但不必对齐内存16字节. 我感觉这两条指令同movaps 和movups 指令一样,不过又不确定. 16. Movlpd movlpd XMM,m64 movlpd m64,XMM 把源存储器64位内容送入目的寄存器低64位,高64位不变,内存变量不必对齐内存16字节

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层处理

内存对齐

C语言内存对齐 分类:C/C++2012-04-05 20:54 1070人阅读评论(1) 收藏举报语言c编译器平台oo 首先由一个程序引入话题: 1//环境:vc6 + windows sp2 2//程序1 3 #include 4 5using namespace std; 6 7struct st1 8 { 9char a ; 10int b ; 11short c ; 12 }; 13 14struct st2 15 { 16short c ; 17char a ; 18int b ; 19 }; 20 21int main() 22 { 23 cout<<"sizeof(st1) is "<

程序的输出结果为: sizeof(st1) is 12 sizeof(st2) is 8 问题出来了,这两个一样的结构体,为什么sizeof的时候大小不一样呢? 本文的主要目的就是解释明白这一问题。 内存对齐,正是因为内存对齐的影响,导致结果不同。 对于大多数的程序员来说,内存对齐基本上是透明的,这是编译器该干的活,编译器为程序中的每个数据单元安排在合适的位置上,从而导致了相同的变量,不同声明顺序的结构体大小的不同。 那么编译器为什么要进行内存对齐呢?程序1中结构体按常理来理解sizeof(st1)和sizeof(st2)结果都应该是7,4(int) + 2(short) + 1(char) = 7 。经过内存对齐后,结构体的空间反而增大了。 在解释内存对齐的作用前,先来看下内存对齐的规则: 1、对于结构的各个成员,第一个成员位于偏移为0的位置,以后每个数据成员的偏移量必须是min(#pragma pack()指定的数,这个数据成员的自身长度) 的倍数。 2、在数据成员完成各自对齐之后,结构(或联合)本身也要进行对齐,对齐将按照#pragma pack指定的数值和结构(或联合)最大数据成员长度中,比较小的那个进行。 #pragma pack(n) 表示设置为n字节对齐。VC6默认8字节对齐 以程序1为例解释对齐的规则:

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

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 外部介质无关接口器件连接

c语言中动态内存申请与释放的简单理解

c语言中动态内存申请与释放的简单理解 在C里,内存管理是通过专门的函数来实现的。与c++不同,在c++中是通过new、delete函数动态申请、释放内存的。 1、分配内存 malloc 函数 需要包含头文件: #include 或 #include 函数声明(函数原型): void *malloc(int size); 说明:malloc 向系统申请分配指定size个字节的内存空间。返回类型是 void* 类型。void* 表示未确定类型的指针。C,C++规定,void* 类型可以强制转换为任何其它类型的指针。 从函数声明上可以看出。malloc 和 new 至少有两个不同: new 返回指定类型的指针,并且可以自动计算所需要大小。比如: int *p; p = new int; //返回类型为int* 类型(整数型指针),分配大小为 sizeof(int); 或: int* parr; parr = new int [100]; //返回类型为 int* 类型(整数型指针),分配大小为sizeof(int) * 100; 而 malloc 则必须由我们计算需要的字节数,并且在返回后强行转换为实际类型的指针。 int* p; p = (int *) malloc (sizeof(int)); 第一、malloc 函数返回的是 void * 类型,如果你写成:p = malloc (sizeof(int)); 则程序无法通过编译,报错:“不能将 void* 赋值给 int * 类型变量”。所以必须通过 (int *) 来将强制转换。 第二、函数的实参为 sizeof(int) ,用于指明一个整型数据需要的大小。如果你写成:

C语言内存对齐

解析C语言结构体对齐(内存对齐问题) C语言结构体对齐也是老生常谈的话题了。基本上是面试题的必考题。内容虽然很基础,但一不小心就会弄错。写出一个struct,然后sizeof,你会不会经常对结果感到奇怪?sizeof的结果往往都比你声明的变量总长度要大,这是怎么回事呢? 开始学的时候,也被此类问题困扰很久。其实相关的文章很多,感觉说清楚的不多。结构体到底怎样对齐? 有人给对齐原则做过总结,具体在哪里看到现在已记不起来,这里引用一下前人的经验(在没有#pragma pack宏的情况下): 原则1、数据成员对齐规则:结构(struct或联合union)的数据成员,第一个数据成员放在offset为0的地方,以后每个数据成员存储的起始位置要从该成员大小的整数倍开始(比如int在32位机为4字节,则要从4的整数倍地址开始存储)。 原则2、结构体作为成员:如果一个结构里有某些结构体成员,则结构体成员要从其内部最大元素大小的整数倍地址开始存储。(struct a里存有struct b,b里有char,int,double等元素,那b应该从8的整数倍开始存储。) 原则3、收尾工作:结构体的总大小,也就是sizeof的结果,必须是其内部最大成员的整数倍,不足的要补齐。 这三个原则具体怎样理解呢?我们看下面几个例子,通过实例来加深理解。 例1:struct { short a1; short a2; short a3; }A; struct{ long a1; short a2; }B; sizeof(A) = 6; 这个很好理解,三个short都为2。 sizeof(B) = 8; 这个比是不是比预想的大2个字节?long为4,short为2,整个为8,因为原则3。 例2:struct A{ int a; char b; short c; }; struct B{ char b; int a; short c; }; sizeof(A) = 8; int为4,char为1,short为2,这里用到了原则1和原则3。 sizeof(B) = 12; 是否超出预想范围?char为1,int为4,short为2,怎么会是12?还是原则1和原则3。

C语言结构体对齐

C语言结构体对齐 C语言结构体对齐也是老生常谈的话题了。基本上是面试题的必考题。内容虽然很基础,但一不小心就会弄错。写出一个struct,然后sizeof,你会不会经常对结果感到奇怪?sizeof的结果往往都比你声明的变量总长度要大,这是怎么回事呢? 开始学的时候,也被此类问题困扰很久。其实相关的文章很多,感觉说清楚的不多。结构体到底怎样对齐? 有人给对齐原则做过总结,具体在哪里看到现在已记不起来,这里引用一下前人的经验(在没有#pragma pack宏的情况下): 原则1、数据成员对齐规则:结构(struct或联合union)的数据成员,第一个数据成员放在offset为0的地方,以后每个数据成员存储的起始位置要从该成员大小的整数倍开始(比如int在32位机为4字节,则要从4的整数倍地址开始存储)。 原则2、结构体作为成员:如果一个结构里有某些结构体成员,则结构体成员要从其内部最大元素大小的整数倍地址开始存储。(struct a里存有struct b,b 里有char,int,double等元素,那b应该从8的整数倍开始存储。) 原则3、收尾工作:结构体的总大小,也就是sizeof的结果,必须是其内部最大成员的整数倍,不足的要补齐。 这三个原则具体怎样理解呢?我们看下面几个例子,通过实例来加深理解。 例1:struct { short a1; short a2; short a3; }A; struct{ long a1; short a2; }B; sizeof(A) = 6; 这个很好理解,三个short都为2。 sizeof(B) = 8; 这个比是不是比预想的大2个字节?long为4,short为2,

#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表示预编译头文件到此为止,后面的头文件不进行预编译。

C语言结构体的字节对齐及指定对齐方式

内存中结构体的内存对齐 一、字节对齐作用和原因: 对齐的作用和原因:各个硬件平台对存储空间的处理上有很大的不同。一些平台对某些特定类型的数据只能从某些特定地址开始存取。比如有些架构的CPU在访问一个没有进行对齐的变量的时候会发生错误,那么在这种架构下编程必须保证字节对齐,其他平台可能没有这种情况,但是最常见的是如果不按照适合其平台要求对数据存放进行对齐,会在存取效率上带来损失。比如有些平台每次读都是从偶地址开始,如果一个int型(假设为32位系统)如果存放在偶地址开始的地方,那么一个读周期就可以读出这32bit,而如果存放在奇地址开始的地方,就需要2个读周期,并对两次读出的结果的高低字节进行拼凑才能得到该32bit 数据,显然在读取效率上下降很多。 二、字节对齐规则: 四个重要的概念: 1.数据类型自身的对齐值:对于char型的数据,其自身对齐值为1,对于short型为2,对于int,float,double类型,其自身对齐值为4个字节。 2.结构体或者类的自身对齐值:其成员中自身对齐值最大的那个值。 3.指定对齐值:#pragma pack (value)时指定的对齐value。 4.数据成员、结构体和类的有效对齐值:自身对齐值和指定对齐值中小的那个值。补充: 1).每个成员分别按自己的方式对齐,并能最小化长度。 2).复杂类型(如结构)的默认对齐方式是它最长的成员的对齐方式,这样在成员是复杂类型时,可以最小化长度。 3).对齐后的长度必须是成员中最大的对齐参数的整数倍,这样在处理数组时可以保证每一项都边界对齐。 #pragma pack(1) struct test { static int a; //static var double m4; char m1; int m3; } #pragma pack() //sizeof(test)=13;

C语言字节对齐

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

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

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

动态内存分配(C语言)

实验报告 实验课程名称:动态内存分配算法 年12月1日

实验报告 一、实验内容与要求 动态分区分配又称为可变分区分配,它是根据进程的实际需要,动态地为之分配内存空间。在实验中运用了三种基于顺序搜索的动态分区分配算法,分别是1.首次适应算法2.循环首次适应算法3.最佳适应法3.最坏适应法分配主存空间。 二、需求分析 本次实验通过C语言进行编程并调试、运行,显示出动态分区的分配方式,直观的展示了首次适应算法循环首次适应算法、最佳适应算法和最坏适应算法对内存的释放和回收方式之间的区别。 首次适应算法 要求空闲分区链以地址递增的次序链接,在分配内存时,从链首开始顺序查找,直至找到一个大小能满足要求的空闲分区为止,然后在按照作业的大小,从该分区中划出一块内存空间,分配给请求者,余下的空余分区仍留在空链中。 优点:优先利用内存中低址部分的空闲分区,从而保留了高址部分的大空闲区,为以后到达的大作业分配大的内存空间创造了条件。 缺点:低址部分不断被划分,会留下许多难以利用的、很小的空闲分区即碎片。而每次查找又都是从低址部分开始的,这无疑又会增加查找可用空闲分区时的开销。

循环首次适应算法 在为进程分配内存空间时,不是每次都从链首开始查找,而是从上次找到的空闲分区的下一个空闲分区开始查找,直到找到一个能满足要求的空闲分区。 优点:该算法能使内存中的空闲分区分布得更均匀,从而减少了查找空闲分区时的开销。 最佳适应算法 该算法总是把能满足要求、又是最小的空闲分区分配给作业,避免大材小用,该算法要求将所有的空闲分区按其容量以从小到大的顺序形成一空闲分区链。 缺点:每次分配后所切割下来的剩余部分总是最小的,这样,在存储器中会留下许多难以利用的碎片。 最坏适应算法 最坏适应算法选择空闲分区的策略正好与最佳适应算法相反:它在扫描整个空闲分区或链表时,总会挑选一个最大的空闲区,从中切割一部分存储空间给作业使用。该算法要求,将所有的空闲分区,按其容量以大到小的顺序形成一空闲分区链。查找时,只要看第一个分区能否满足作业要求即可。 优点:可使剩下的空闲区不至于太小,产生碎片的可能性最小,对中小作业有利,同时,最坏适应算法查找效率很高。 缺点:导致存储器中缺乏大的空闲分区 三、数据结构 为了实现动态分区分配算法,系统中配置了相应的数据结构,用以描述空闲分区和已分配分区的情况,常用的数据结构有空闲分区表和空闲分区链 流程图

相关文档
最新文档