C语言深度解剖 (二)
前言
C语言的水深不见底,好在一些前辈们已经将很多雷区探了一遍
这里分享一下我在学习 《C语言深度解剖》 过程中的一些笔记和心得
概要
- TOC
{:toc}
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 object
x=7;
//*p1=7; //error C2166: l-value specifies const object
p1=&y;
y=8;
//*p2=8; //error C2166: l-value specifies const object
p2=&x;
z=9;
*p3=10;
//p3=&x; //error C2166: l-value specifies const object
u=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 00111001
p->a[1]=0x38; //第二个字节被赋为了0x38,于是变成了 00000000 00000000 00111000 00111001
x=u.i; //14393,因为是小端模式,低位在前,所以合成结果为14393
}
同样是在小端的存储模式中,如果将共用体定义在main之内,会不会是同样的结果呢?
结果是不一样的
main()
{
union
{
int i;
char a[2];
}*p,u; //一个 union 会占用四个字节,并且会被初始化,这是在局部区域,至于初始化为什么值,不同编译器不一样,VC++6.0为 -858993460 即 11001100 11001100 11001100 11001100 ,如果是在全局区,会被初始化为全0
int x;
p=&u;
p->a[0]=0x39; //第一个字节被赋为了0x39,于是变成了 11001100 11001100 11001100 00111001
p->a[1]=0x38; //第二个字节被赋为了0x38,于是变成了 11001100 11001100 00111000 00111001
x=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) 个的地址转化为整型指针后赋给ptr1
int *ptr2=(int *)((int)a+1); //这个操作是将a地址转化为整型数,然后将值加1,然后将这个整型数转化为整型指针赋给ptr2
printf("%x,%x\n",ptr1[-1],*ptr2); //将内容以十六进制的形式输出
}
这里有一个以a开头的内容矩阵
00000001 00000000 00000000 00000000
00000010 00000000 00000000 00000000
00000011 00000000 00000000 00000000
00000100 00000000 00000000 00000000
00000101 00000000 00000000 00000000
00000000 00000000 00000000 00000000
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, //3
blue=5,
yellow, //6
green_red=10,
green_blue //11
}val=11;
int i=sizeof(red); //4
int j=sizeof(green); //4
int k=sizeof(yellow); //4
int x=sizeof(val); //4
}