C语言杂谈

C语言程序处理过程

预处理:宏定义展开、头文件展开、条件编译,这里并不会检查语法
编译:检查语法,将预处理后文件编译生成汇编文件
汇编:将汇编文件生成目标文件(二进制文件)
链接:将目标文件链接为可执行程序
程序只有在运行才加载到内存(由系统完成),但是某个变量具体分配多大,是在编译阶段就已经确定了,换句话说,在编译阶段做完处理后,程序运行时系统才知道分配多大的空间,所以,很多时候说,这个变量的空间在编译时就分配(确定)了。

 

字符串操作

1、字符串基本操作
1)字符串初始化
/* C语言没有字符串类型,用字符数组模拟
C语言字符串以数字0,或字符 '\0' 结尾,数字 0 和字符 '\0' 等价 */

char str1[100] = { 'a', 'b', 'c', 'd' }; //没赋值的自动以数字0填充
char str2[] = { 'a', 'b', 'c', 'd' }; //数组长度为4,结尾没有数字0
char str4[] = "abcdef"; //常用赋值方式,栈区
char *p = "abcdef"; //文字常量区,内容不允许被修改

char *buf = (char *)malloc(100); //堆区
strcpy(buf, "abcd"); //"abcd"拷贝到buf指向的内存区域中

2)sizeof和strlen区别
//使用字符串初始化,常用
char buf8[] = "abc";
//strlen: 测字符串长度,不包含数字0,字符'\0'
//sizeof:测数组长度,包含数字0,字符'\0'
printf("strlen = %d, sizeof = %d\n", strlen(buf8), sizeof(buf8));

char buf9[100] = "abc";
printf("strlen = %d, sizeof = %d\n", strlen(buf9), sizeof(buf9));

3)'\0' 后面最好别跟数字,因为几个数字合起来有可能是一个转义字符
//\012相当于\n
char str[] = "\0129";
printf("%s aaaa\n", str);

4)字符'\0', 数字0, 字符'0', NULL的区别
a) 字符'\0' ASCII码值为 0 的字符
字符'\0' 和 数字 0 是等级的,'\0'中'\'是转义字符

char buf[100];
//下面是等级,在数组第10个位置设置字符串结束符
buf[10] = 0;
buf[10] = '\0';

b) 字符'0'是字符串的某个字符内容为'0', ASCII码值为 48 的字符
char buf[100];
buf[0] = '0'; //第0个字符为字符 '0'

c) NULL 标准头文件(stdio.h)的宏 其值为数字 0

 

const的使用

1)const声明变量为只读(不可更改)
const int a = 10;
a = 100; //error

技巧:去掉指针类型看constant修饰谁

 //修饰*,指针指向能变,指针指向的内存不能变

 //修饰指针变量,指针指向的内存,指针指向不能变

char buf[100] = "abcdef";
const char *p = buf; //类似于文字常量区 char *p = "123445";
char const *p = buf; //修饰*,指针指向能变,指针指向的内存不能变
//p[0] = '1'; //error
p = "123456"; //ok

char * const p1 = buf; //修饰指针变量,指针指向的内存,指针指向不能变
//p1 = "123456"; //error
p1[0] = '1'; //ok

const char * const p2 = buf; //p2, 只读

 数组类型

int a[] = { 1, 3, 5 }; //3个元素
a: 数组首行首元素地址,一级指针
&a: 整个数组的首地址,二级指针

首行首元素地址和首行(整个一维数组)地址值虽然是一样,但是它们的步长不一样
a+1: 跳过1元素,一元素为4字节,步长4个字节
&a+1: 跳过整个数组,整个数组长度为 3*4 = 12,步长 3 * 4 = 12

sizeof(a): 传参为:数组首行首元素地址,测数组(int [3])的长度,3 * 4 = 12
sizeof(a[0]): 传参为:数组首元素(不是地址),每个元素为int类, 4字节
sizeof(&a):传参为:一维数组整个数组的地址(首行地址),编译器当做指针类型,4字节

(重要)首行地址 --> 首行首元素地址(加*)
&a:首行地址
*&a -> a : 首行首元素地址


//数组也是一种数据类型,类型本质:固定大小内存块别名
//由元素类型和内存大小(元素个数)共同决定 int a[5]   int[5]
//可以通过typedef定义数组类型

//有typedef:类型
//没有typedef:变量
typedef int ARRARY[5]; //定义了一个名字为ARRARY的数组类型(int[5]类型)
//等价于typedef int (ARRARY)[5];

//根据数组类型,定义变量
//ARRARY的位置替代为d,去掉typedef,int d[5]
ARRARY d; //相当于int d[5];

 

结构体字节对齐规则

内存对齐的原则

默认情况下,数据成员的对齐规则(以最大的类型字节为单位)

 

当然,字节对齐可以通过程序控制,采用指令:

#pragma pack(xx)    

 

#pragma pack(1)     //1字节对齐

#pragma pack(2)     //2字节对齐

#pragma pack(4)     //4字节对齐

#pragma pack(8)     //8字节对齐

#pragma pack(16)    //16字节对齐

原则1数据成员的对齐规则(以最大的类型字节为单位)。

结构体(struct)的数据成员,第一个数据成员放在offset为0的地方,以后每个数据成员存放在offset为该数据成员大小的整数倍的地方(比如int在32位机为4字节,则要从4的整数倍地址开始存储)

 

 1 struct
 2 {
 3     int a;
 4     short b;
 5 }A;
 6 //对齐单位为 4 个字节(结构体A中最大的类型为int(4个字节))
 7 //偏移量:
 8 a: 4*0 = 0
 9 b: 2*2 = 4 
10 
11 sizeof(A) = 8;

 

 1 struct
 2 {
 3     int a;
 4     char b;
 5     short c;
 6 }A;
 7 //对齐单位 4 个字节
 8 sizeof(A) = 8;
 9 a: 4*0 = 0 //偏移0个字节
10 b: 1*4 = 4  //偏移4个字节
11 c: 2*3 = 6
 

 

 

 

 

原则2:结构体作为成员的对齐规则。

 

如果一个结构体B里嵌套另一个结构体A,则结构体A应从offset为A内部最大成员的整数倍的地方开始存储。

(struct B里存有struct A,A里有char,int,double等成员,那A应该从8的整数倍开始存储。),结构体A中的成员的对齐规则仍满足原则1、原则2。

 

注意

 

1. 结构体A所占的大小为该结构体成员内部最大元素的整数倍,不足补齐。

 

2. 不是直接将结构体A的成员直接移动到结构体B中

 

 1 struct A
 2 {
 3     int a;
 4     double b;
 5     float c;
 6 };
 7 
 8 struct
 9 {
10     char e[2];
11     int f;
12     double g;
13     short h;
14     struct A i;
15 }B;
16 //对齐单位 8 个字节
17 sizeof(B) = 48;
18 //普通成员偏移量
19 e: 1*0 = 0
20 f: 4*1 = 4
21 g: 8*1 = 8
22 h: 2*8 = 16
23 结构体起点坐标:
24    8*3 = 24
25 //结构体成员偏移量
26 a: 24 + 4*0 = 24
27 b: 24 + 8*1 = 32
28 c: 24 + 4*4 = 40

 

 

原则3:收尾工作

结构体的总大小,也就是sizeof的结果,必须是其内部最大成员的整数倍,不足的要补齐。

 

原则4:指定对齐单位

 

 1 #pragma pack(2)  //指定对齐单位为2个字节
 2 typedef struct
 3 {
 4     int a; 
 5     char b;
 6     short c;
 7     char d;
 8 }A;
 9 a: 2*0 = 0
10 b: 1*4 = 4
11 c: 2*3 = 6
12 d: 1*8 = 8
13 sizeof(A) = 10;

 

 

 

 

 

有关不完整类型的字节对齐

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

1) 一个位域必须存储在同一个字节中,不能跨两个字节。如一个字节所剩空间不够存放另一位域时,应从下一单元起存放该位域。

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

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

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

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

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

 1 //以 4 字节对齐
 2 struct A
 3 {
 4     int     a1:5; 
 5     int     a2:9; 
 6     char    c;
 7     int     b:4;
 8     short   s;
 9 }B;
10 
11 sizeof(B) = 16
12 
13 a1 + a2 = 14 位,少于 32位,4字节
14 a1+a2存放在一起:4*0 = 0
15 c: 1 * 4 = 4
16 b: 4 * 2 = 8 //所占的大小为该结构体成员内部最大元素的整数倍(4对齐),不足补齐。
17 s: 2 * 6 = 12 

 

 

 1 struct A
 2 {
 3     int     a1 : 5;
 4     int     a2 : 9;
 5     char    c;
 6     int     b : 4;
 7     short   s;
 8 }B;
 9 sizeof(B) = 12;
10 
11 a1+a2 = 14 (小于32位,4字节)
12 a1+a2: 2 * 0 =0;
13 c: 1 * 4 = 4;
14 b:  2 * 3 = 6;  //4超过2字节,以2字节为单位
15 s:2 * 5 = 10

 

 

 

 

posted @ 2020-09-08 22:03  北极星!  阅读(153)  评论(0编辑  收藏  举报