C语言深度解剖 (二)

2016-12-01 10:21:54来源:作者:镜缘浮影人点击

前言

C语言的水深不见底,好在一些前辈们已经将很多雷区探了一遍

这里分享一下我在学习 《C语言深度解剖》过程中的一些笔记和心得

概要 const

准确来说 const是只读的意思,而不是常量

const 初始目的是为了继承预编译指令的优点而消除它的缺点

Item const #define 速度 快 慢 内存 少 多 执行 编译过程中 编译过程前 类型 有类型 无类型(替换) 所属 变量(只读) 常量 void main(){const int a=1;int const b=2; //前面两种方式相同const int c; //如果在定义时不赋初值,后面将没有机会,因为它是只读的int const array[5]={1,2,3,4,5};const int brray[5]={1,2,3,4,5};int x=1,y=2,z=3,u=4;const int *p1=&x; //p1可变,*p1只读int const *p2=&y; //p2可变,*p2只读int *const p3=&z; //p3只读,*p3可变const int *const p4=&u; //p4只读,*p4只读//a=3; //error C2166: l-value specifies const object//b=4; //error C2166: l-value specifies const object//c=5; //error C2166: l-value specifies const object//array[0]=6; //error C2166: l-value specifies const object//brray[0]=6; //error C2166: l-value specifies const objectx=7; //*p1=7; //error C2166: l-value specifies const objectp1=&y;y=8;//*p2=8; //error C2166: l-value specifies const objectp2=&x;z=9;*p3=10;//p3=&x; //error C2166: l-value specifies const objectu=11;//*p4=12; //error C2166: l-value specifies const object//p4=&x; //error C2166: l-value specifies const object}

看起来有点混乱,不过这里有一个记忆和理解的方法:

先忽略类型名(编译器解析的时候也是忽略类型名),我们看const离哪个近。”近水楼台先得月”,离谁近就修饰谁

判断时忽略括号中的类型

const (int) *p; //const修饰 *p, *p是指针指向的对象,不可变 (int) const *p; //const修饰 *p, *p是指针指向的对象,不可变 (int)*const p; //const修饰 p, p不可变, p指向的对象可变 const (int) *const p; //前一个const修饰 *p,后一个const修饰 p,指针 p和 p指向的对象都不可变 volatile

volatile提醒编译器它后面所定义的变量随时都有可能改变,因此编译后的程序每次需要存储或读取这个变量的时候,都会直接从变量地址中读取数据。如果没有volatile关键字,则编译器可能优化读取和存储,可能暂时使用寄存器中的值,如果这个变量由别的程序更新了的话,将出现不一致的现象

遇到这个关键字定义的变量,编译器对访问的代码不再进行优化,如何理解这个优化,其实就是为了提升访问速度而不从源地址读取,从缓存中读取,如何理解它的作用,有了这个修饰后可以保证对特殊地址的稳定访问

void main(){volatile const int i=0;const volatile int j=0;//i=2; //error C2166: l-value specifies const object//j=3; //error C2166: l-value specifies const object}

volatile和 const同时修饰时,以 const的属性为主

union

union型数据所占的空间等于其最大的成员所占的空间

对 union型成员的存取都是相对于该共用体基地址偏移量为0处开始,也就是共用体的访问不论对哪个变量的存取都是从 union的首地址位置开始的

在小端的存储模式中:

union {int i;char a[2];}*p,u; //一个 union 会占用四个字节,并且会被初始化为 00000000 00000000 00000000 00000000 ,由于是在全局区,所以会被这样初始化main(){int x;p=&u;p->a[0]=0x39; //第一个字节被赋为了0x39,于是变成了 00000000 00000000 00000000 00111001p->a[1]=0x38; //第二个字节被赋为了0x38,于是变成了 00000000 00000000 00111000 00111001x=u.i; //14393,因为是小端模式,低位在前,所以合成结果为14393}

同样是在小端的存储模式中,如果将共用体定义在main之内,会不会是同样的结果呢?

结果是不一样的

main(){union{int i;char a[2];}*p,u; //一个 union 会占用四个字节,并且会被初始化,这是在局部区域,至于初始化为什么值,不同编译器不一样,VC++6.0为 -858993460 即 11001100 11001100 11001100 11001100 ,如果是在全局区,会被初始化为全0int x;p=&u;p->a[0]=0x39; //第一个字节被赋为了0x39,于是变成了 11001100 11001100 11001100 00111001p->a[1]=0x38; //第二个字节被赋为了0x38,于是变成了 11001100 11001100 00111000 00111001x=u.i; //-859031495,因为是小端模式,低位在前,所以合成结果为 -859031495}

这个例子说明了以下几点:

同样没有初始化,全局变量和局部变量初值是不一样的,全局为0,局部和编译器还有系统相关 全局变量和局部变量可能并不存在于同一样区域

那这个例子有什么启示呢?

如果要使结果一致,最好给定义的变量赋初值,否则会产生意想不到的结果

存储模式

系统的存储分为两种模式: 大端模式和 小端模式

大端模式(big_endian):字数据的高字节存储在低地址中,而字数据的低字节则存放在高地址中 小端模式(little_endian):字数据的高字节存储在高地址中,而字数据的低字节则存放在低地址中

使用int和char还有共用体的存储特性,可以获取系统的存储模式

小端模式:

int i=1;

00000000 00000000 00000000 00000001

大端模式:

int i=1;

00000001 00000000 00000000 00000000

只要使用char获取起始地址的一个字节,判断是否为1,就可以知道系统是否为小端模式

void main(){union check{int i;char ch;}c;int a;c.i=1;a=c.ch; //如果a为1就说明系统是小端模式,如果a为0就说明是大端模式}

分析下面的输出

#include <stdio.h>void main(){int a[5]={1,2,3,4,5};int *ptr1=(int *)(&a+1); //这个操作是在a地址的基础上跳过5 * sizeof(int) 或者 sizeof(a) 个的地址转化为整型指针后赋给ptr1int *ptr2=(int *)((int)a+1); //这个操作是将a地址转化为整型数,然后将值加1,然后将这个整型数转化为整型指针赋给ptr2printf("%x,%x/n",ptr1[-1],*ptr2); //将内容以十六进制的形式输出}

这里有一个以a开头的内容矩阵

ptr1 指向的是 [5][0] ,ptr1[-1] 指向的是 [4][0] 也就是a[4] 与之后的四个字节,也就是 00000101 00000000 00000000 00000000为 0x5

ptr2 指向的是 [0][1],与之后的四个字节,也就是 00000000 00000000 00000000 00000010为 0x2000000

enum

如果不赋值则会从被赋初值的那个常量开始依次加1,如果都没有赋值,它们的值从0开始依次递增1

void main(){enum color{green=2,red, //3blue=5, yellow, //6green_red=10,green_blue //11}val=11;int i=sizeof(red); //4int j=sizeof(green); //4int k=sizeof(yellow); //4int x=sizeof(val); //4}

最新文章

123

最新摄影

微信扫一扫

第七城市微信公众平台