水滴石穿C语言之C语言的底层操作2.docx
《水滴石穿C语言之C语言的底层操作2.docx》由会员分享,可在线阅读,更多相关《水滴石穿C语言之C语言的底层操作2.docx(101页珍藏版)》请在淘文阁 - 分享文档赚钱的网站上搜索。
1、水滴石穿C语言之C语言的底层操作概述 C语言的内存模型基本上对应了现在von Neumann (冯诺伊曼)计算机的实际存 储模型,很好的达到了对机器的映射,这是C/C+适合做底层开发的主要原因,另外,C语 言适合做底层开发还有另外一个原因,那就是C语言对底层操作做了很多的的支持,提供了 很多比较底层的功能。下面结合问题分别进行阐述。问题:移位操作在运用移位操作符时,有两个问题必须要清楚:(1)、在右移操作中,腾空位是填0还是符号位;(2)、什么数可以作移位的位数。答案与分析:和。”是指将变量中的每一位向右或向左移动,其通常形式为:右移:变量名移位的位数左移:变量名移位的位数经过移位后,一端的位
2、被挤掉,而另一端空出的位以0填补,在C语言中的移位不是 循环移动的。(1)第一个问题的答案很简单,但要根据不同的情况而定。如果被移位的是无符号数, 则填0。如果是有符号数,那么可能填0或符号位。如果你想解决右移操作中腾空位的填 充问题,就把变量声明为无符号型,这样腾空位会被置Oo(2)第二个问题的答案也很简单:如果移动n位,那么移位的位数要不小于0 ,并且 一定要小于n o这样就不会在一次操作中把所有数据都移走。比如,如果整型数据占32位,n是一整型数据,则n31和n0都合法,而n 32和n -1都不合法。注意即使腾空位填符号位,有符号整数的右移也不相当与除以。为了证明这一点,我们可以想一下-
3、1 1不可能为0。问题:位段结构struct RPR_ATD_TLV_HEADER ULONG resl:6;ULONG type:10:ULONG resl:6;ULONG length:10;;位段结构是一种特殊的结构,在需按位访问一个字节或字的多个位时,位结构比按位运算符更 加方便。位结构定义的一般形式为:struct位结构名数据类型变量名:整型常数;数据类型变量名:整型常数;位结构变量;其中:整型常数必须是非负的整数,范围是015,表示二进制位的个数,即表示有多少位。 变量名是选择项,可以不命名,这样规定是为了排列需要。例如:下面定义了一个位结构。struct unsigned inc
4、on: 8; /*incon 占用低字节的 07 共 8 位*/unsigned txcolor: 4;/*txcolor 占用高字节的 03 位共 4 位*/unsigned bgcolor: 3;/*bgcolor 占用高字节的 46 位共 3 位*/unsigned blink: 1; /*blink 占用高字节的第 7 位*/ch;位结构成员的访问与结构成员的访问相同。例如:访问上例位结构中的bgcolor成员可写成:ch.bgcolor位结构成员可以与其它结构成员一起使用。按位访问与设置,方便&节省例如:struct infochar name8;int age;struct add
5、r address;float pay;unsigned state: 1;unsigned pay: 1;workers;上例的结构定义了关于一个工从的信息。其中有两个位结构成员,每个位结构成员只有一位, 因此只占一个字节但保存了两个信息,该字节中第一位表示工人的状态,第二位表示工资是否 已发放。由此可见使用位结构可以节省存贮空间。注意不要超过值限制问题:字节对齐让偶们先来看下面这个结构体:struct stulint a;char b;来看看sizeof (stu)的结果为多少?怎么是8啊?你先别急,再来看下一个例子:struct stu2(char b;int a;)这个sizeof (
6、stu2)是多少?怎么还是8啊?现在创建一个结构体变量 stu2 s2 a ”, 0xl2345678h) ; stul si 0x12345678, a 运行DEGUG,怎么样发现了什么?在第一个结构体中char b的后面内存有三个字节是添了数据的.也就是这样78 56 34 12 61 ccCC CC而在第二个结构体中CHAR B的后面内存中也添加了数据.61 cc cc cc 78 56 34 12这又是怎么回事呢?需要字节对齐当然有设计者的考虑了,原来这样有助于加快计算机的存取速度,否则就得多花指令周期了。所以,编译器通常都会对结构体进行处理,让宽度为2的基本数据类型(short 等)
7、 都位于能被2整除的地址上,让宽度为4的基本数据类型(int等)都位于能被4整除的地址上。 正是因为如此两个数中间就可能需要加入填充字节,所以结构体占的内存空间就增长了。其实字节对齐的细节和具体编译器实现相关,但一般而言,满足三个准则:1)结构体变量的首地址能够被其最宽基本类型成员的大小所整除;2)结构体每个成员相对于结构体首地址的偏移量都是成员大小的整数倍,如有需要编译器会 在成员之间加上填充字节;例如上面第二个结构体变量的地址空间。3)结构体的总大小为结构体最宽基本类型成员大小的整数倍,如有需要编译器会在最末一个 成员之后加上填充字节。例如上面第一个结构体变量。(哎呀!知道!真多嘴!)现在
8、就可以解释上面的问题了,第一个结构体变量中成员变量最宽为4(SIZEOF(INT) = 4),所 以S1变量首地址必须能被4整除。(不信你试试!)S1的大小也应该为4的整数倍。但是现在 si中有4+1的空间,所以为了满足第三个条件就在char b的后面在加上三个字节的空间以凑 够8个字节空间。第二个结构体变量S2中成员变量最大宽度为4,而且按照以前的理解int a的 地址和s2的地址相差5个字节,但是为了满足第而个条件(相差的距离偏移地址必须是4 的整数倍)所以在char b的后面添加了三个字节的空间以保证int a的偏移地址是4的整数倍即 为4。至于涉及到结构体嵌套的问题,你也可以用上述方法
9、总结的,只不过你把被嵌套的结构体 在原地展开就行了,不过在计算偏移地址的时候被嵌套的结构体是不能原地展开的必须当作整 体。嘿嘿!偶申明一点,上述三条建议不是偶说的,是做编译器的工程师总结出来的,偶只是 借用而已。我在使用VC编程的过程中,有一次调用DLL中定义的结构时,发觉结构都乱掉了, 完全不能读取正确的值,后来发现这是因为DLL和调用程序使用的字节对齐选项不同,那么我 想问一下,字节对齐究竟是怎么一回事?答案与分析: 为了能使CPU对变量进行高效快速的访问,变量的起始地址应该具有某些特性,即所谓的“对 齐”。例如对于4字节的int类型变量,其起始地址应位于4字节边界上,即起始地址能够被4
10、整除。关于字节对齐:1、当不同的结构使用不同的字节对齐定义时,可能导致它们之间交互变得很困难。2、在跨CPU进行通信时,可以使用字节对齐来保证唯一性,诸如通讯协议、写驱动程序 时候寄存器的结构等。三种对齐方式:1、自然对齐方式(Natural Alignment):与该数据类型的大小相等。2、指定对齐方式:pragma pack(8) 指定 Align 为 8;pragma pack () 恢复到原先值3、实际对齐方式:Actual Align = min ( Order Align, Natual Align )对于复杂数据类型(比如结构等):实际对齐方式是其成员最大的实际对齐方式:Actu
11、al Align = max( Actual alignl, 2, 3, )编译器的填充规律:1、成员为成员Actual Align的整数倍,在前面加Padding。成员Actual Align = min(结构Actual Align,设定对齐方式)2、结构为结构Actual Align的整数倍,在后面加Padding.例子分析:pragma pack(8) 指定 Align 为 8struct STestl(char chi;long lol;char ch2; testl;ttpragma pack ()现在Align of STestl = 4 , sizeof STestl = 12
12、 (4*3)testl在内存中的排列如下(FF为padding ):00 04 08 12 01 FF FF FF 01 01 01 01 01 FF FF FFchi - lol - ch2ttpragma pack(2) 指定 Align 为 2struct STest2(char ch3;STestl test; test2;#pragma pack ()现在 Align of STestl = 2, Align of STest2 = 2 , sizeof STest2 = 14 (7*2)test2在内存中的排列如下:00 04 08 12 02 FF 01 FF FF FF 01
13、01 01 01 01 FF FF FFch3 chi lol ch2注意事项:1、这样一来,编译器无法为特定平台做优化,如果效率非常重要,就尽量不要使用#pragma pack,如果必须使用,也最好仅在需要的地方进行设置。2、需要加pack的地方一定要在定义结构的头文件中加,不要依赖命令行选项,因为如果 很多人使用该头文件,并不是每个人都知道应该pack。这特别表现在为别人开发库文件时,如 果一个库函数使用了 struct作为其参数,当调用者与库文件开发者使用不同的pack时,就会 造成错误,而且该类错误很不好查。3、在VC及BC提供的头文件中,除了能正好对齐在四字节上的结构外,都加了 pa
14、ck,否则 我们编的Windows程序哪一个也不会正常运行。4、在ttpragma pack(n)后一定不要include其他头文件,若包含的头文件中改变了 align 值,将产生非预期结果。5、不要多人同时定义一个数据结构。这样可以保证一致的pack值。问题:按位运算符C语言和其它高级语言不同的是它完全支持按位运算符。这与汇编语言的位操作有些相似。C 中按位运算符列出如下:操作符作用&位逻辑与I位逻辑或C位逻辑异或-位逻辑反右移左移注意:1、按位运算是对字节或字中的实际位进行检测、设置或移位,它只适用于字符型和整数型 变量以及它们的变体,对其它数据类型不适用。2、关系运算和逻辑运算表达式的结
15、果只能是1或Oo而按位运算的结果可以取0或1以外 的值。要注意区别按位运算符和逻辑运算符的不同,例如,若x=7,则x&8的值为真(两个非 零值相与仍为非零),而x&8的值为0。3、 I与&与&, 与!的关系&、I和操作符把它们的操作数当作一个为序列,按位单独进行操作。比如:10 & 12 = 8,这是因为操作符把10和12当作二进制描述1010和1100 ,所以只有当两个操作数 的相同位同时为1时,产生的结果中相应位才为1。同理,10 | 12 = 14 ( 1110 ),通过补 码运算,10 = -11 ( 11. 110101 )o以多少为一个位序列&、| |和!操作符把它们的操 作数当作
16、真或假,并且用0代表假,任何非0值被认为是真。它们返回1代表真”, 0代表假,对于&和II操作符,如果左侧的操作数的值就可以决定表达式的值,它们根 本就不去计算右侧的操作数。所以,! 10是0 ,因为10非0 ; 10a& 12是1 ,因为10和 12均非0 ; 10 | | 12也是1 ,因为10非0。并且,在最后一个表达式中,12根本就没 被计算,在表达式10 | f()中也是如此。水滴石穿C语言之extern声明辨析1基本解释extern可以置于变量或者函数前,以标示变量或者函数的定义在别的文件中,提示编译器 遇到此变量和函数时在其他模块中寻找其定义。另外,extern也可用来进行链接指
17、定。2问题:extern变量在一个源文件里定义了一个数组:char a6;在另外一个文件里用下列语句进行了声明:extern char *a;请问,这样可以吗?答案与分析:1)、不可以,程序运行时会告诉你非法访问。原因在于,指向类型T的指针并不等价于类 型T的数组。extern char *a声明的是一个指针变量而不是字符数组,因此与实际的定义不同, 从而造成运行时非法访问。应该将声明改为extern char a 2)、例子分析如下,如果a口 = abed”,则外部变量a=0x61626364 (abed的ASCH码值), *2显然没有意义,如下图:a-lhE6l62S3M0x6162636
18、4显然a指向的空间(0x61626364)没有意义,易出现非法内存访问。3)、这提示我们,在使用extern时候要严格对应声明时的格式,在实际编程中,这样的错 误屡见不鲜。4)、extern用在变量声明中常常有这样一个作用,你在*. c文件中声明了一个全局的变量, 这个全局的变量如果要被引用,就放在*.h中并用extern来声明。3问题:extern函数1常常见extern放在函数的前面成为函数声明的一部分,那么,C语言的关键字extern在 函数的声明中起什么作用?答案与分析:如果函数的声明中带有关键字extern,仅仅是暗示这个函数可能在别的源文件里定义,没 有其它作用。即下述两个函数声明
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 水滴石穿 语言 底层 操作
限制150内