文章目录
- 1. const(常量指针、指针常量)
- 2. static
- 3. extern
- 4. 指针数组和数组指针
- 5. 结构体对齐
- 6. int / uint取值范围、二进制形式与转换、负数表示
- 7. '\0','0',"0",0之间的区别
- 8. 类型自动转换
- 9. 内存结构
- 10. 大小端
- 参考资料
1. const(常量指针、指针常量)
int *p1; // p1是一个指针,指向int类型
const int *p2; // p2是一个常量指针,指向const int类型,p2可指向其他地址
int const *p3; // p3和p2相同,只是写法不同
int *const p4 = addr; // p4是一个指针常量,指向int类型,p4不能再指向其他地址
const int *const p5 = addr; // p5是一个指针常量,它指向常量,const同时修饰类型与指针,p5和p5指向的地址均不可修改
- 指针常量
本质上一个常量,指针用来说明常量的类型,表示该常量是一个指针类型的常量。
在指针常量中,指针自身的值是一个常量,不可改变,始终指向同一个地址。
在定义的同时必须初始化。 - 常量指针
常量指针本质上是一个指针,常量表示指针指向的内容,说明该指针指向一个“常量”。
在常量指针中,指针指向的内容是不可改变的,指针看起来好像指向了一个常量。
2. static
通常有两种情况:
- 修饰变量
静态全局变量:全局变量前加static修饰,该变量就成为了静态全局变量。全局变量在整个工程都可以被访问(一个文件中定义,其它文件使用的时候添加extern关键字声明 ),而在添加了static关键字之后,这个变量就只能在本文件内被访问了。因此,在这里,static的作用就是限定作用域
。
静态局部变量:局部变量添加了static修饰之后,该变量就成为了静态局部变量。局部变量在离开了被定义的函数后,就会被销毁,而当使用static修饰之后,它的作用域就一直到整个程序结束。因此,在这里static的作用就是限定生命周期
。 - 修饰函数
修饰函数则该函数成为静态函数,只能被本文件中的其他函数调用,不能
被同一程序的其他文件中的函数调用,即函数的作用域仅限于本文件,而不能被其它文件调用。(限定作用域
)
- 在C语言中,关键字static有三个明显的作用:
(1) 在函数体,一个被声明为静态的变量在这一函数被调用过程中维持其值不变。
(2) 在模块内(但在函数体外),一个被声明为静态的变量可以被模块内所有函数访问,但不能被模块外其它函数访问。它是一个本地的全局变量。
(3) 在模块内,一个被声明为静态的函数只可被这一模块内的其它函数调用。这个函数被限制在声明它的模块的本地范围内使用。
3. extern
函数的声明和定义方式默认都是extern的,即函数默认是全局的。因此,为了修饰当前文件中的内部函数,static关键字出场了。
使用static和extern修饰变量的时候,变量的生命周期是一样的,不同的是变量的作用域。
关键字 | 生命周期 | 作用域 |
---|---|---|
extern | 静态(程序结束后释放) | 外部(整个程序) |
static | 静态(程序结束后释放) | 内部(仅编译单元,一般指单个源文件) |
auto, register | 函数调用(调用结束后释放) | 无 |
- extern函数的目的
(1)extern可置于变量或者函数前,以表示变量或者函数的定义在别的文件中,提示编译器遇到此变量和函数时在其他模块中寻找其定义;
(2)取代include "*.h"来声明函数;
(3)extern “C”,C++在编译时为解决函数多态问题,会将函数名和参数联合起来生成一个中间的函数名,而C语言则不会,因此会造成链接时找不到对应的函数的情况,故在C++中采用extern“C”{ }声明,可以让.cpp文件使用.c文件中的函数。
4. 指针数组和数组指针
核心是看符号优先级来区分是指针还是数组:()>[]> *
一个数组里含有的元素是指针类型时,它就是指针数组。
// 注意优先级:()>[]> *
int (*p)[5]; // (数组指针)定义一个指向含有5个元素的一维数组的指针,即它是一个指针,指向一维数组
int *p[5]; // (指针数组)定义一个含有5个int型指针的数组,即它是一个数组,内部元素是指针
- 解析
由于优先级()>[]> *
,(*p)是一个整体,表示指针,它指向int [5]数组;p[5]是一个整体,表示数组,数组元素是int *。
5. 结构体对齐
- 对齐步骤
(1)首先,结构体各成员对齐;
(2)然后,结构体总体对齐。 - 对齐规则
(1)第一个数据成员存放的地址为结构体变量偏移量为0的地址处;
(2)其他结构体成员存放的地址为min{自身对齐值,指定对齐值}
的最小整数倍的地址处;
(3)总体对齐时,字节大小为min{所有成员中自身对齐值最大的,指定对齐值}
的整数倍。 - 基本概念
数据自身对齐值:char为1,short为2,float为4,int通常为4等。
结构体或类的自身对齐值:其成员中自身都旗帜最大的那个值。
指定对齐值:#pragma pack(value)
时指定的对齐value。
数据成员、结构体有效对齐值:min{自身对齐值,指定对齐值}
- 实例
在一个四字节对齐的32位系统中,有如下结构体,其中sizeof(SmartFlag)=?
struct tagSmart
{
char flag1; // 自身对齐值为1字节
int (*left_tree)[3]; // 数组指针,其本身是一个指针,因此指针的自身对齐值为4字节
struct tagSmart *right_tree[2]; // 指针数组,此数组中含有2个struct tagSmart *型元素,自身对齐值为2*sizeof(struct tagSmart *)=2*4=8字节
char flag3; // 自身对齐值为1字节
char flag4; // 自身对齐值为1字节
} SmartFlag[4];
首先,进行结构体各成员对齐,遵循原则min{自身对齐值,指定对齐值}
的最小整数倍的地址处,其中指定对齐值为4字节:
flag1的有效对齐值为min{1,4}=1
,存放地址:1*0=0
,它放在结构体偏移量为0的地址处;
(*left_tree)的有效对齐值为’min{4,4}=4’,存放地址:4*0=0
,不可用,4*1=4
可用,即放在偏移量为4的地址处;
right_tree[2]的有效对齐值为min{8,4}=4
,存放地址:4*2=8
可用,即放在偏移量为8的地址处;
flag3同上,有效对齐值为1,存放地址:1*16=16
,即放在偏移量为16的地址处;
flag4放在偏移量为17的地址处;
其中整体占18字节;
然后,进行总体对齐,字节大小为min{8,4}=4
的整数倍,4*5=20
,因此结构体对齐占20字节;
最后sizeof(SmartFlag)=20*4=80
字节。
6. int / uint取值范围、二进制形式与转换、负数表示
- int / uint的取值范围
int8: -128 ~ 127
int16: -32768 ~ 32767
int32: -2147483648 ~ 2147483647
int64: -9223372036854775808 ~ 9223372036854775807
uint8: 0 ~ 255
uint16: 0 ~ 65535
uint32: 0 ~ 4294967295
uint64: 0 ~ 18446744073709551615
理解:
- 有符号
int8占1个字节(Byte) ,即8个二进制位(Bit);
8个二进制位就有2^8 = 256种组合(可以存储256个数);
int8为有符号,所以正数和负数将平分256个数。256 / 2 = 128
负数为128个,最小为-128,正数为128个,0占一个,最大为127。 - 无符号:
如果是uint8(8Bit无符号-没有负数) 2^8 = 256
0占一个数,所以最大是255。
- 基础知识
正数的原码、反码、补码相同。等于真值对应的机器码。
负数的原码等于机器码,反码为原码的符号位不变,其余各位按位取反。补码为反码+1。 - 负数的互转
负数以绝对值的补码形式表达。
需要先获得其绝对值的原码,再得反码,再得补码。
原码:一个整数,按照绝对值大小转换成的二进制数,称为原码。
反码:将二进制数按位取反(1变0,0变1),所得的新二进制数称为原二进制数的反码。
补码:反码加1称为补码。
-29的二进制为:
绝对值:29
原码:00011101
反码:11100010
补码:11100010 + 00000001
表达:11100011
想要反推出负数的十进制,只需要按同样的方法将表达反推原码,原码的十进制数 * -1 即可:
-29的原码二进制为:
表达:11100011
反码:00011100
补码:00011100 + 00000001
原码:00011101
换算成10进制为 从低位到高位开始计算:
0 0 0 1 1 1 0 1
0*2^7 + 0*2^6 + 0*2^5 + 1*2^4 + 1*2^3 + 1*2^2 + 0*2^1 + 1*2^0
0 +0 +0 +16 +8 +4 +0 +1
=29 * -1
= -29
7. ‘\0’,‘0’,“0”,0之间的区别
其中'\0'
,'0'
是字符,"0"
是字符串常量,0
是整形常量。
对于字符,C语言中依据ASCII码对应进行存储,\0
对应的是空字符,即ASCII码表中的0(Null),'0'
对应的是表中的48,它可以参与相关运算,例如字符和数字的转换时:8+'0','8'-3
。
字符常量由单引号括起来;字符串常量由双引号括起来。
字符常量只能是单个字符;字符串常量则可以含一个或多个字符。
- sizeof和strlen中的区别和用法
\0是字符串的结束符,sizeof和strlen中到底算不算\0字符,容易搞混淆。
sizeof是C语言中的一个单目运算符,用来计算数据类型所占空间的大小,单位为字节,在计算字符串的空间大小时,包含了结束符\0的位置;
strlen是一个函数,用来计算字符串长度,使用时需要引用头文件#include <string.h>,不包含\0,即计算\0之前的字符串长度。
8. 类型自动转换
- 类型转换原则
(1)参与运算的类型不同,先转换为相同类型再运算;
(2)数据类型向数据长度增长的方向转换,char->short->int->unsigned int ->long
,float->double
;
(3)赋值运算时,赋值号右侧的类型向左侧的类型转换;
(4)同一类型,有符号数和无符号数运算时,有符号数向无符号数转变。 - 实例
在C语言中下面哪些表达式为真?
unsigned int a = 20;
int b = 13;
int k = b - a;
// b - a运算时首先b会转为无符号整形计算,并通过补码进行计算,得到一个很大的数,即k是一个很大的负数。
k < (unsigned int)b + a; // 右侧是无符号数,因此k要先转为无符号数再进行运算,k转换后得到一个很大的无符号数,大于右侧,假
k < (int)(b + a); // b+a被强制转为int,因此两边采用int型进行比较,k为负数,真
k < b + (int)a; // 同上
- 无符号数之间的运算
无符号数之间进行的加减法运算,是通过补码来进行的。比如a - b,实质上是a补 + (-b补)。
9. 内存结构
BSS段(存放未初始化的全局变量,不占用执行程序大小,内容由操作系统初始化) |
---|
数据段(存放已初始化的全局变量,包含初始化的静态变量,即全局变量和static变量) |
代码段(存放程序执行代码的内存区域,大小在程序运行前已经确定,且通常属于只读) |
堆(存放进程运行中被动态分配的内存段,大小不固定,malloc分配的为此内存) |
栈(临时创建的局部变量(不含static声明的变量),在函数被调用时,其参数也会被压栈) |
- .bss段存放
未初始化的全局变量(算数类型和指针类型);
未初始化的static变量(不论在函数外部还是在函数内部定义);
未初始化的常量;
初始化为0的全局算数类型变量,初始化为NULL的全局指针类型变量;
初始化为0的static变量(不论在函数内部定义还是在函数外部定义); - .data段存放
有初始值(不为0)的全局变量;
有初始值(不为0)的static变量,(不论static变量在函数内部定义还是在函数外部定义); - .text段存放
可执行指令
有初始值(不论初始值是否是0)的常量
10. 大小端
大端(存储)模式,是指数据的低位保存在内存的高地址中,而数据的高位,保存在内存的低地址
中;
小端(存储)模式,是指数据的低位保存在内存的低地址中,而数据的高位,保存在内存的高地
址中。
参考资料
- 指针常量和常量指针
- c语言中的static关键字的作用
- C/C++中的 extern 和extern“C“关键字的理解和使用(对比两者的异同)
- 指针数组和数组指针(非常易懂)
- C语言–结构体内存对齐规则
- C语言中sizeof()和strlen()的区别(详解版)
- int / uint 的 取值范围、二进制表示形式、与十进制转换方法
- 无符号数的减法