c语言基础复习

如何阅读内核代码

简单的基础复习

C语言:数组、函数、指针、内存对齐模式、大小端问题、野指针、内存泄露、static、register、define、typedef、struct、union 等一些关键字的考察。

容易忘的基本语法

基本语法一览

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
// 常数: #define 关键词
#define DAYS_IN_YEAR 365

// 以枚举的方式定义常数
enum days {SUN = 1, MON, TUE, WED, THU, FRI, SAT};
// MON自动被定义为2,TUE被定义为3,以此类推。


#include <stdio.h>
// include <尖括号>间的文件名是C标准库的头文件。
// 标准库以外的头文件,使用双引号代替尖括号。
#include "my_header.h"


// 你的程序的入口是一个返回值为整型的main函数
int main(int argc,char *argv[]){}
//argc参数表示了命令行中参数 的个数(注意:文件名本身也算一个参数)
// argv参数是字符串指针数组, 其各元素值为命令行中各字符串 (参数均按字符串处理)的首地址。指针数组的长度即为参数个数。数组元素初值由系统自动赋予。


// char类型一定会占用1个字节,但是其他的类型却会因具体机器的不同而各异
int x_int = 0;// int型(整型)变量一般占用4个字节
short x_short = 0;// short型(短整型)变量一般占用2个字节
char x_char = 0;// char型(字符型)变量会占用1个字节
char y_char = 'y'; // 字符变量的字面值需要用单引号包住
long x_long = 0;// long型(长整型)一般需要4个字节到8个字节;
long long x_long_long = 0; //而long long型则至少需要8个字节(64位)
float x_float = 0.0;// float一般是用32位表示的浮点数字
double x_double = 0.0;// double一般是用64位表示的浮点数字

// 整数类型也可以有无符号的类型表示。这样这些变量就无法表示负数
// 但是无符号整数所能表示的范围就可以比原来的整数大一些
unsigned short ux_short;
unsigned int ux_int;
unsigned long long ux_long_long;

// size_t是一个无符号整型,表示对象的尺寸,至少2个字节
size_t size = sizeof(a++); // a++ 不会被演算,siezeof直接算a的size

// 数组必须要被初始化为具体的长度
// 可以用下面的方法把数组初始化为0:
char my_array[20] = {0};
// 多维数组
int multi_array[2][5] = {
{1, 2, 3, 4, 5},
{6, 7, 8, 9, 0}
}
// 获取元素
int array_int = multi_array[0][2]; // => 3

// 字符串就是以 NUL (0x00) 这个字符结尾的字符数组,
// NUL可以用'\0'来表示.
// (在字符串字面量中我们不必输入这个字符,编译器会自动添加的)
char a_string[20] = "This is a string";
printf("%s\n", a_string); // %s 可以对字符串进行格式化
/*
也许你会注意到 a_string 实际上只有16个字节长.
第17个字节是一个空字符(NUL)
而第18, 19 和 20 个字符的值是未定义。
*/
// 字符串增、减
char *s = "iLoveC"
int j = 0;
s[j++]; // "i" 返回s的第j项,然后增加j的值。

操作符

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
///////////////////////////////////////
// 操作符
///////////////////////////////////////


// C中没有布尔类型,而是用整形替代
// (C99中有_Bool或bool。)
// 0为假, 其他均为真. (比较操作符的返回值总是返回0或1)
3 == 2; // => 0 (false)
3 != 2; // => 1 (true)

// C不是Python —— 连续比较不合法
int a = 1;
int between_0_and_2 = 0 < a < 2;// 错误
int between_0_and_2 = 0 < a && a < 2;// 正确

// 逻辑运算符适用于整数
!3; // => 0 (非)
!0; // => 1
1 && 1; // => 1 (且)
0 && 1; // => 0
0 || 1; // => 1 (或)
0 || 0; // => 0

// 三元式、条件表达式 ( ? : )
z = (a > b) ? a : b; // 10 “若a > b返回a,否则返回b。”

// 位运算
~0x0F; // => 0xF0 (取反)
0x0F & 0xF0; // => 0x00 (和)
0x0F | 0xF0; // => 0xFF (或)
0x04 ^ 0x0F; // => 0x0B (异或)
0x01 << 1; // => 0x02 (左移1位)
0x02 >> 1; // => 0x01 (右移1位)

// 对有符号整数进行移位操作要小心 —— 以下未定义:
// 有符号整数位移至符号位 int a = 1 << 32
// 左移位一个负数 int a = -1 << 2
// 移位超过或等于该类型数值的长度
// int a = 1 << 32; // 假定int32位

控制结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
///////////////////////////////////////
// 控制结构
///////////////////////////////////////

// if(){}
// else if(){}
// else{}

// While循环
// while(){}
// do{}while()

//for (int i = 0; i <= 5; i++)

// 多重分支:switch()
switch (some_integral_expression) {
case 0: // 标签必须是整数常量表达式
do_stuff();
break; // 如果不使用break,控制结构会继续执行下面的标签
case 1:
do_something_else();
break;
default:
// 假设 `some_integral_expression` 不匹配任何标签
fputs("error!\n", stderr);
exit(-1);
break;
}

类型转换

1
2
3
4
5
int x_hex = 0x01; // 可以用16进制字面量赋值
// 整数型和浮点型可以互相转换
printf("%f\n", (float)100); // %f 格式化单精度浮点
printf("%lf\n", (double)100); // %lf 格式化双精度浮点
printf("%d\n", (char)100.0);

指针

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
;-)
;)
:)
int x = 0;
printf("%p\n", &x); // 用 & 来获取变量的地址

// 指针类型在声明中以*开头
int* px, not_a_pointer; // px是一个指向int型的指针
px = &x; // 把x的地址保存到px中
printf("%p\n", (void *)px); // => 输出内存中的某个地址
printf("%zu, %zu\n", sizeof(px), sizeof(not_a_pointer));
// => 在64位系统上打印“8, 4”。

// 要得到某个指针指向的内容的值,可以在指针前加一个*来取得(取消引用)
// 注意: 是的,这可能让人困惑,'*'在用来声明一个指针的同时取消引用它。
printf("%d\n", *px); // => 输出 0, 即x的值

// 指针的增减多少是依据它本身的类型而定的
// (这被称为指针算术)
printf("%d\n", *(x_ptr + 1)); // => 打印 19
printf("%d\n", x_array[1]); // => 打印 19


// 你也可以通过标准库函数malloc来实现动态分配
// 这个函数接受一个代表容量的参数,参数类型为`size_t`
// 系统一般会从堆区分配指定容量字节大小的空间
// (在一些系统,例如嵌入式系统中这点不一定成立,C标准对此未置一词。)
int *my_ptr = malloc(sizeof(*my_ptr) * 20);
for (xx=0; xx<20; xx++) {
*(my_ptr + xx) = 20 - xx; // my_ptr[xx] = 20-xx
} // 初始化内存为 20, 19, 18, 17... 2, 1 (类型为int)

// malloc分配的区域需要手动释放
// 否则没人能够再次使用这块内存,直到程序结束为止
free(my_ptr);


// 字符串通常是字符数组,但是经常用字符指针表示
// 一个优良的习惯是使用`const char *`来引用一个字符串字面量,
// 因为字符串字面量不应当被修改(即"foo"[0] = 'a'犯了大忌)
const char* my_str = "This is my very own string";
printf("%c\n", *my_str); // => 'T'


// 如果字符串是数组,(多半是用字符串字面量初始化的)
// 情况就不一样了,字符串位于可写的内存中
char foo[] = "foo";
foo[0] = 'a'; // 这是合法的,foo现在包含"aoo"

函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
/*
函数是按值传递的。当调用一个函数的时候,传递给函数的参数
是原有值的拷贝(数组除外)。

可以通过指针来传递引用,这样函数就可以更改值
例子:字符串本身翻转
*/

// 类型为void的函数没有返回值
void str_reverse(char *str_in){
char tmp;
int ii = 0;
size_t len = strlen(str_in); // `strlen()`` 是C标准库函数
for(ii = 0; ii < len / 2; ii++){
tmp = str_in[ii];
str_in[ii] = str_in[len - ii - 1]; // 从倒数第ii个开始
str_in[len - ii - 1] = tmp;
}
}

// 如果引用函数之外的变量,必须使用extern关键字
int i = 0;
void testFunc() {
extern int i; // 使用外部变量 i
}

// 使用static确保external变量为源文件私有
static int i = 0; // 其他使用 testFunc()的文件无法访问变量i
void testFunc() {
extern int i;
}
//**你同样可以声明函数为static**


///////////////////////////////////////
// 函数指针
///////////////////////////////////////
/*
在运行时,函数本身也被存放到某块内存区域当中
函数指针就像其他指针一样(不过是存储一个内存地址) 但却可以被用来直接调用函数,
并且可以四处传递回调函数
但是,定义的语法初看令人有些迷惑
例子:通过指针调用str_reverse
*/
void str_reverse_through_pointer(char *str_in) {
// 定义一个函数指针 f.
void (*f)(char *); // 签名一定要与目标函数相同
f = &str_reverse; // 将函数的地址在运行时赋给指针
(*f)(str_in); // 通过指针调用函数
// f(str_in); // 等价于这种调用方式
}

用户自定义类型和结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
// Typedefs可以创建类型别名,不同于define
typedef int my_type;
my_type my_type_var = 0;


// struct是数据的集合,成员依序分配,按照
// 编写的顺序
struct rectangle {
int width;
int height;
};
// 一般而言,以下断言不成立:
// sizeof(struct rectangle) == sizeof(int) + sizeof(int)
//这是因为structure成员之间可能存在潜在的间隙(为了对齐)请看下文介绍

void function_1(){

struct rectangle my_rec;

// 通过 . 来访问结构中的数据
my_rec.width = 10;
my_rec.height = 20;

// 你也可以声明指向结构体的指针
struct rectangle *my_rec_ptr = &my_rec;

// 通过取消引用来改变结构体的成员...
(*my_rec_ptr).width = 30;

// ... 或者用 -> 操作符作为简写提高可读性
my_rec_ptr->height = 10; // Same as (*my_rec_ptr).height = 10;
}

// 你也可以用typedef来给一个结构体起一个别名
typedef struct rectangle rect;


int area(rect r){//<--非指针传递
return r.width * r.height;
}
// 如果struct较大,你可以通过指针传递,避免
// 复制整个struct。
int area(const rect *r){//<--指针传递
return r->width * r->height;
}

内存对齐

普遍来说

是以几个字节为单位的内存对齐

如微机中8086若从偶地址读8字节,可以一个周期读完,而

空指针 野指针 无类型指针

  • 空指针是被赋值为NULL的指针
  • 野指针是没有被初始化的指针。
  • 无类型指针,是指void定义的指针,没有确定类型,可以转化为其他类型,适用场景更广泛。