exercise 10:

  1. for(INITIALIZE, CONDITION, INCREMENT)

  2. https://stackoverflow.com/questions/12959415/comma-operator-in-condition-of-loop-in-c 一般的逗号可以被看做一个二元操作符,先进行逗号左边内容的计算,然后再进行逗号右边内容的计算,并返回逗号右边计算的内容作为返回值。那么有什么一般的用处呢?可以在for循环的条件部分改造成i < (x++, x/2) 这样实际上仅从的操作就是,x++ , 判断i < (x + 1) / 2。上面的提问实际上等价于for(i, 5, i++)

  3. 将NULL作为字符串指针之后,就是将其指向了一块不对应任何内容的零内存空间,最后打印的效果是(null)

  4. 可以

exercise 11:

  1. 简单的编程实现如下所示:![ex11_1](C:\Users\Junrong Huang\Desktop\ysyx-proj\pre-learning\ex_records\ex11_1.png)

  2. 在1的基础上,结合3的条件,改造后的代码如下所示:

![ex11_2_3](C:\Users\Junrong Huang\Desktop\ysyx-proj\pre-learning\ex_records\ex11_2_3.png)

  1. 根据输入测试输出:

![ex11_3](C:\Users\Junrong Huang\Desktop\ysyx-proj\pre-learning\ex_records\ex11_3.png)

在输出远多于states大小的情况下,正确倒序输出并且valgrind没有检测到任何漏洞

  1. 并没有真正复制这些字符串,实际上仅仅是将argv中存储的字符串地址指针复制给了states字符串数组。

exercise 12:

  1. 额外的布尔运算符有||或运算,否定运算!,> ,< , >=, <= 。当然,也包括逗号运算符。

  2. 多测试一些输入就可,如a b c;abc abcd abcde…

  3. ![ex12_3](C:\Users\Junrong Huang\Desktop\ysyx-proj\pre-learning\ex_records\ex12_3.png)

  4. 不正确,程序本身的第一个参数一定./ex11,而用户输入的第一个参数实际上在argv数组里从下标1开始计算,也即用户输入的参数个数是argc-1。这句话应该改成”You have input no argument“

exercise 13:

编译器处理switch语句的过程:

  1. 如图所示:

![ex13_1_1](C:\Users\Junrong Huang\Desktop\ysyx-proj\pre-learning\ex_records\ex13_1_1.png)

![ex13_1](C:\Users\Junrong Huang\Desktop\ysyx-proj\pre-learning\ex_records\ex13_1.png)

  1. 如图所示,只需要在,左边执行letter的赋值即可

![ex13_2](C:\Users\Junrong Huang\Desktop\ysyx-proj\pre-learning\ex_records\ex13_2.png)

  1. 如图所示:

![ex13_3](C:\Users\Junrong Huang\Desktop\ysyx-proj\pre-learning\ex_records\ex13_3.png)

![ex13_3_1](C:\Users\Junrong Huang\Desktop\ysyx-proj\pre-learning\ex_records\ex13_3_1.png)

  1. 如图所示:

![ex13_4_1](C:\Users\Junrong Huang\Desktop\ysyx-proj\pre-learning\ex_records\ex13_4_1.png)

  1. 这样可以使得字母y或Y出现在用户输入的参数字符串前三个位置的时候不被打印出来。这样避免了y被认为vowel的情况。当把break放到if里面的时候,会导致当字母y/Y出现在前三个位置的时候无法进入if,进而无法直接退出switch。这样最后会进入到default中,输出错误的结果。如图所示:

![ex13_5](C:\Users\Junrong Huang\Desktop\ysyx-proj\pre-learning\ex_records\ex13_5.png)

exercise 14:

notes:

  1. ctype.h头文件包含的内容:https://en.cppreference.com/w/cpp/header/cctype
  2. blank与space的区别,在c里面的blank是包含tab、换行、空格等一系列visual blank的内容,而space则对应ASCII码中的一个space。
  3. 移除前向声明会导致编译器无法正常编译,报“implicit decalaration of function”,即若在一个函数a的实现里面调用了b函数,则b函数的定义或者预先声明必须在a的实现之前。
  4. 如果在main调用print_arguments时,对argc+1,那么它在访问argv的时候就会越过argv数组的最后一个元素,通过valgrind对错误进行检测,可知argv的下一个内存比特内容为空,那么print_letters就会接受一个空指针,并尝试打印0x0处的字符串。而0x0并不是程序合法的地址空间,从而造成了访问越界Segmentation fault.

extra credits:

1.

![ex14_1](C:\Users\Junrong Huang\Desktop\ysyx-proj\pre-learning\ex_records\ex14_1.png)

2.

![ex14_2](C:\Users\Junrong Huang\Desktop\ysyx-proj\pre-learning\ex_records\ex14_2.png)

3.

![ex14_3](C:\Users\Junrong Huang\Desktop\ysyx-proj\pre-learning\ex_records\ex14_3.png)

  1. Detailed information on K&R syntax: https://jameshfisher.com/2016/11/27/c-k-and-r/ https://stackoverflow.com/questions/3092006/function-declaration-kr-vs-ansi

exercise 15:

notes: Revision on Pointer Operations

Increment/Decrement or Addition/Substraction numerically.

p++ —> p = p + sizeof(type(p))

ptr++, ptr–, ptr = ptr + 5, ptr = ptr - 3

同理addition/subtraction也是处理+- i * sizeof(int …)

指针的加减法,加减的单位数字实质上是指针指向元素类型的多少。在地址上永远要对指针运算的数值乘以类型的大小sizeof(…)

指针之间的减法有效当且仅当两个指针的类型相同,对于同int类型指针p1, p2。p1-p2的结果实际上是: (p1 - p2) / sizeof(int); 也即p1的地址减去p2的地址,除以int类型的大小4,得到p1,p2之间间隔的int类型元素的个数。

Detailed Article: https://www.geeksforgeeks.org/pointer-arithmetics-in-c-with-examples/

编程题:

![ex15_ex1](C:\Users\Junrong Huang\Desktop\ysyx-proj\pre-learning\ex_records\ex15_ex1.png)

extra credits:

1.

![ex15_1](C:\Users\Junrong Huang\Desktop\ysyx-proj\pre-learning\ex_records\ex15_1.png)

2.

![ex15_2](C:\Users\Junrong Huang\Desktop\ysyx-proj\pre-learning\ex_records\ex15_2.png)

  1. 见ex15_1.c,用指针改写了所有使用数组的地方

  2. 对ex10进行修改,如下所示:

![ex15_4](C:\Users\Junrong Huang\Desktop\ysyx-proj\pre-learning\ex_records\ex15_4.png)

  1. ex15.c中第二个循环的实现就将获取值与获取地址结合到了一起,外层for循环中的循环变量i通过指针运算做到了获取地址,也即cur_name + i获取到了指针的地址;然后通过一层解引用*(cur_name + i)来对获取到的地址解引用,从而获取了值。

![ex15_6code](C:\Users\Junrong Huang\Desktop\ysyx-proj\pre-learning\ex_records\ex15_6code.png)

![ex15_6](C:\Users\Junrong Huang\Desktop\ysyx-proj\pre-learning\ex_records\ex15_6.png)

7.

![ex15_7](C:\Users\Junrong Huang\Desktop\ysyx-proj\pre-learning\ex_records\ex15_7.png)

![ex15_7_code](C:\Users\Junrong Huang\Desktop\ysyx-proj\pre-learning\ex_records\ex15_7_code.png)

8.

![ex15_8](C:\Users\Junrong Huang\Desktop\ysyx-proj\pre-learning\ex_records\ex15_8.png)

![ex15_8_test](C:\Users\Junrong Huang\Desktop\ysyx-proj\pre-learning\ex_records\ex15_8_test.png)

前三种都是while循环方便,只有最后一种写法while循环比较方便。

exercise 16

Notes:

  1. strdup函数可以用来拷贝一个传入的字符串s,返回一个指向复制出来相同字符串的指针。这个字符串是通过malloc申请内存来获取的。在这一讲的练习里面,Person_create函数中会先malloc一块空间给Person结构体,然后在strdup中其实也会对字符串进行空间上的malloc。因此,最后删除Person结构体的时候,我们需要同时将Person结构体中所有malloc的空间free掉。
  2. 注意到,如果我们不想显式地free内存,这回潜在地导致内存泄漏,那么就需要引入LibGC库,并用GC_malloc
  3. printf函数中的\t是垂直格式限定符,会在垂直方向上进行格式的控制。%p格式控制符可以打印出某个变量的地址。注意这里的%p要搭配取地址符来实现,或者说其对应打印的对象必须是一个指针。
  4. 创建结构体、摧毁结构体的过程可以预先设计好两个函数。这里借鉴了面向对象设计的思想,实际上,每个结构体对应面向对象程序设计中的一个类。那么,我们创建结构体的过程实际上就可以被包含在构造函数之中。而摧毁结构体的过程实际上就是析构函数。

如何使它崩溃:

  1. 传入NULL给Person_destroy实际上会导致Abort,因为Person_destroy首先进行的就是一个assert,然后不符合就会导致。
  2. 向valgrind传递–leak-check=full参数来进行内存泄漏的测试

![ex16_err1](C:\Users\Junrong Huang\Desktop\ysyx-proj\pre-learning\ex_records\ex16_err1.png)

这里可以看到,在valgrind中报错指出了内存泄漏和损失问题。并指出了造成这一问题的函数,是main里面调用Person_create中strdup里面调用了malloc导致的。

  1. 当我们向Person_print传入NULL时,会显示地址0x0不是在程序映射到的地址段,访存越界!导致Segmentation Fault!

extra_credits:

  1. 在栈上创建结构体的方式相当直接,只需要直接用struct Person x = {…}来进行初始化即可
  2. 对于x指针,只需要将x->y替换为*(x).y即可,而对于一般的结构体变量x,直接x.y初始化即可
  3. 使用指针来将结构体传入函数,实际上做的是“传址调用”,而直接将结构体传给其他函数,可以通过传值调用来实现。

具体修改的代码如下所示:

![ex16_1](C:\Users\Junrong Huang\Desktop\ysyx-proj\pre-learning\ex_records\ex16_1.png)

![ex16_2](C:\Users\Junrong Huang\Desktop\ysyx-proj\pre-learning\ex_records\ex16_2.png)

exercise17:

notes:

  1. errno - number of last error 这里涉及到具体的stdin, stdout和stderr之间的关系了。Linux将这三种数据流视作文件,一般来说他们的文件描述符分别为0-stdin,1-stdout, 2-stderr。stdout在正常的时候作为输出流,而当程序出错的时候,stderr就会打印输出在bash中。perror所做的就是这样的一件事,向stderr填充错误信息,这样当程序出错的时候,我们就可以及时地得到反馈了。

例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <stdio.h>
#include <stdlib.h>

int main(void)
{
FILE *fh;

if ((fh = fopen("mylib/myfile","r")) == NULL) //当没有成功打开文件的时候
{
perror("Could not open data file");//向stderr输出内容
abort();//主动引发程序报错
}
}
  1. 对这部分源码的解读:
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
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
#include <stdio.h>
#include <assert.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>

#define MAX_DATA 512
#define MAX_ROWS 100

struct Address {
int id;
int set;
char name[MAX_DATA];
char email[MAX_DATA];
};

struct Database {
struct Address rows[MAX_ROWS];
};

struct Connection {
FILE *file;
struct Database *db;
};

void die(const char *message)
{
if(errno) { //errno存储着上一次类似系统调用、库函数所报错的参数
perror(message);//
} else {
printf("ERROR: %s\n", message);
}

exit(1);
}

void Address_print(struct Address *addr)
{
printf("%d %s %s\n",
addr->id, addr->name, addr->email);
}

void Database_load(struct Connection *conn) //加载Database
{
//fread第一个参数是dst,第二个参数是读取的单位的字节大小,第三个参数是指从源中读出的单位的个数,最后一个参数格式为FILE *fp, 实际上是指明了需要从fp这里读取数据。
//fread读取数据时保证rc <= 1, rc != 1也就是读取失败了。
int rc = fread(conn->db, sizeof(struct Database), 1, conn->file);
if(rc != 1) die("Failed to load database."); //读取失败,调用die进行报错。
}

struct Connection *Database_open(const char *filename, char mode)
{
struct Connection *conn = malloc(sizeof(struct Connection));
if(!conn) die("Memory error"); //一条关系malloc不了,报错!

conn->db = malloc(sizeof(struct Database));
if(!conn->db) die("Memory error"); //关系的数据库malloc不了,报错!

if(mode == 'c') {
conn->file = fopen(filename, "w");
} else {
conn->file = fopen(filename, "r+");

if(conn->file) {
Database_load(conn);
}
}

if(!conn->file) die("Failed to open the file");

return conn;
}

void Database_close(struct Connection *conn)
{
if(conn) {
if(conn->file) fclose(conn->file);
if(conn->db) free(conn->db);
free(conn);
}
}

void Database_write(struct Connection *conn)
{
rewind(conn->file);//等价于 fseek(stream, 0, SEEK_SET)
//相应的,fseek里面whence的SEEK_SET对应文件开头,SEEK_CUR代表文件当前indicator所在的位置,SEEK_END对应文件结束。
//将文件内容写入数据库
int rc = fwrite(conn->db, sizeof(struct Database), 1, conn->file);
if(rc != 1) die("Failed to write database.");

//洗缓存,直接输出内容到stdout
rc = fflush(conn->file);
if(rc == -1) die("Cannot flush database.");
}

void Database_create(struct Connection *conn)
{
int i = 0;

for(i = 0; i < MAX_ROWS; i++) {
// make a prototype to initialize it
struct Address addr = {.id = i, .set = 0};
// then just assign it
conn->db->rows[i] = addr;
}
}

void Database_set(struct Connection *conn, int id, const char *name, const char *email)
{
struct Address *addr = &conn->db->rows[id];
if(addr->set) die("Already set, delete it first");

addr->set = 1;
// WARNING: bug, read the "How To Break It" and fix this
char *res = strncpy(addr->name, name, MAX_DATA);
// demonstrate the strncpy bug
if(!res) die("Name copy failed");

res = strncpy(addr->email, email, MAX_DATA);
if(!res) die("Email copy failed");
}

void Database_get(struct Connection *conn, int id)
{
struct Address *addr = &conn->db->rows[id];

if(addr->set) {
Address_print(addr);
} else {
die("ID is not set");
}
}

void Database_delete(struct Connection *conn, int id)
{
struct Address addr = {.id = id, .set = 0}; //这里简单地传值复制了一下,用局部变量addr来初始化conn所指向Database的第id row的地址内容
conn->db->rows[id] = addr;
}

void Database_list(struct Connection *conn)
{
int i = 0;
struct Database *db = conn->db;


for(i = 0; i < MAX_ROWS; i++) {
struct Address *cur = &db->rows[i];

if(cur->set) {
Address_print(cur);
}
}
}

int main(int argc, char *argv[])
{
if(argc < 3) die("USAGE: ex17 <dbfile> <action> [action params]");

char *filename = argv[1];
char action = argv[2][0];
struct Connection *conn = Database_open(filename, action);
int id = 0;

if(argc > 3) id = atoi(argv[3]);
if(id >= MAX_ROWS) die("There's not that many records.");

switch(action) {
case 'c':
Database_create(conn);
Database_write(conn);
break;

case 'g':
if(argc != 4) die("Need an id to get");

Database_get(conn, id);
break;

case 's':
if(argc != 6) die("Need id, name, email to set");

Database_set(conn, id, argv[4], argv[5]);
Database_write(conn);
break;

case 'd':
if(argc != 4) die("Need id to delete");

Database_delete(conn, id);
Database_write(conn);
break;

case 'l':
Database_list(conn);
break;
default:
die("Invalid action, only: c=create, g=get, s=set, d=del, l=list");
}

Database_close(conn);

return 0;
}
  1. https://www.tutorialspoint.com/c_standard_library/c_function_fflush.htm 关于fflush和缓冲区的内容。

printf默认是将内容打印到stdout这个流,而一般的输入输出流stdin、stdout都是有缓冲区的。对于输出来说,printf实际上将要打印的内容先输入到stdout的缓冲区,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <stdio.h>
#include <string.h>

int main () {

char buff[1024];

memset( buff, '\0', sizeof( buff ));

fprintf(stdout, "Going to set full buffering on\n");
setvbuf(stdout, buff, _IOFBF, 1024); //这里开了全缓冲,大小

fprintf(stdout, "This is tutorialspoint.com\n");
fprintf(stdout, "This output will go into buff\n");
fflush( stdout );

fprintf(stdout, "and this will appear when programm\n");
fprintf(stdout, "will come after sleeping 5 seconds\n");

sleep(5);

return(0);
}
  1. strncpy的设计缺陷,它只会讲src的前n个字节做复制,若src的前n个字节中不包含”\0”,那么dst作为字符串被拷贝的情况下就可能不以”\0”作为结束,导致不符合一般的字符串格式。
  2. 在database_set函数里面,是通过strncpy来进行邮箱、姓名的复制的。而strncpy执行复制的过程中一般没有包含’\0’。在我们的Address结构体中可知name和email的数组最大是512.那么当我们输入的字符串长度超过512的时候,会将输入字符串的前512个字符复制到address的name/email里。这就导致我们最终复制的字符串不是以’\0’结尾的。

测试数据:

1
./ex17 db.dat s 4 hjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxfly hjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxflyhjrxfly@163.com

![ex17_b1](C:\Users\Junrong Huang\Desktop\ysyx-proj\pre-learning\ex_records\ex17_b1.png)

![ex17_b2](C:\Users\Junrong Huang\Desktop\ysyx-proj\pre-learning\ex_records\ex17_b2.png)

在修改最后一个为’\0’后,可见后者打印出来的字符串是要比前者短的。

1
./ex17 db.dat s 4 aaaabbbbaaaabbbbaaaabbbbaaaabbbbaaaabbbbaaaabbbbaaaabbbbaaaabbbbaaaabbbbaaaabbbbaaaabbbbaaaabbbbaaaabbbbaaaabbbbaaaabbbbaaaabbbbaaaabbbbaaaabbbbaaaabbbbaaaabbbbaaaabbbbaaaabbbbaaaabbbbaaaabbbbaaaabbbbaaaabbbbaaaabbbbaaaabbbbaaaabbbbaaaabbbbaaaabbbbaaaabbbbaaaabbbbaaaabbbbaaaabbbbaaaabbbbaaaabbbbaaaabbbbaaaabbbbaaaabbbbaaaabbbbaaaabbbbaaaabbbbaaaabbbbaaaabbbbaaaabbbbaaaabbbbaaaabbbbaaaabbbbaaaabbbbaaaabbbbaaaabbbbaaaabbbbaaaabbbbaaaabbbbaaaabbbbaaaabbbbaaaabbbbaaaabbbbaaaabbbbaaaabbbbaaaabbbbaaaabbbbaaaabbbbccccddddeeeeffff hjrxfly@163.com

上述样例存入数据库,之后访问得到的结果为:

![ex17_b3](C:\Users\Junrong Huang\Desktop\ysyx-proj\pre-learning\ex_records\ex17_b3.png)

也就是说,存入的时候,addr->name保存了输入姓名的前512个字符,addr->email正常存储。而在系统存储内部,name和email是连续存储的。也就是说,在打印name的时候,因为name字符串没有以’\0’结尾,那么在输出的时候,打印完name所有内容后也不会停止,会接着打印email的内容直到触摸到第一个’\0’,这也就解释了为什么name后面会多一个邮箱地址。后面输出的name的长度,也是name+email的长度。这个现象就是当输入超过512个字符时的结果。

Extra credits:

1.

![ex17_1](C:\Users\Junrong Huang\Desktop\ysyx-proj\pre-learning\ex_records\ex17_1.png)

这里die额外维护一个传入的Connection即可,对于conn非NULL时分别尝试fclose和free。

细节:

1
2
3
//假设conn->db->rows为一个address数组的起始指针
conn->db->rows[id] = addr <=>等价于 *(conn->db->rows + id) = addr;
以数组形式访问得到的直接结果是指针解引用后的值,而非该元素实际的地址(指针变量存储的地址内容)

2.

出现bug:

id:1的地址为0x5567fb3b28e8,set为0x5567fb3b28ec

程序设计的逻辑上是有问题的。

我在database_create的时候想当然地去malloc空间来给database,但是实际上,database进行实时交互地内存在每一次操作的时候需要单独malloc来进行分配。也就是说,实际上我们的database去申请空间的过程应该在database_open中实现。至于每次改动的内容,应该由database_write写入到文件里来实现保存。

当我们正确创建database后,实际上往文件conn->file写入的内容只有一个Database结构体的内容,也就是说,我们只写入了一个类型为struct Address *的指针,和max_data、max_rows的内容…而没有写入具体的,每一行的内容。因为每一个具体的Address行内容都是在create的时候malloc的,在堆区!当create结束后,实际上堆区内存就会被释放,这部分内存在下面s的时候也不会被检索到!也就是说,上一次申请的内存在下一次调用程序输入内容的时候是非法的,所以会segmentation fault。

我们的做法是,在Database_open中来做堆区内存的申请,每次结束的时候将对数据库的修改写回文件中!

我们设计的db.dat文件格式如下所示:

其中有max_rows个Address,文件模块,后面都跟着对应的字符串。

1
File begin: |struct Database|struct Address id:0|id:0 name string|id:0 email string|struct Address id:1|....

最终得到的程序代码为:

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
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
#include <stdio.h>
#include <assert.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>

#define MAX_DATA 512
#define MAX_ROWS 100

struct Address {
int id;
int set;
char *name;//Set to the pointers for creating various scale db.dat
char *email;
};

struct Database {
struct Address *rows;
__uint32_t max_data, max_rows;
};

struct Connection {
FILE *file;
struct Database *db;
};

void die(const char *message, struct Connection *conn)
{
if(errno) {
perror(message);
} else {
printf("ERROR: %s\n", message);
}
if(conn != NULL && conn->file) fclose(conn->file);
if(conn != NULL && conn->db) free(conn->db);
exit(1);
}

void Address_print(struct Address *addr)
{
printf("%d %s %s\n",
addr->id, addr->name, addr->email);
// printf("The length of addr->name is %d \n", (int)strlen(addr->name));
}

void Database_load(struct Connection *conn)
{
int rc = fread(conn->db, sizeof(struct Database), 1, conn->file);
//Read the index struct item of Database
if(rc != 1) die("Failed to load database.", conn); //rc = read count
int max_r = conn->db->max_rows, max_d = conn->db->max_data;
conn->db->rows = (struct Address *)calloc(max_r, sizeof(struct Address));
for (int i = 0 ; i < max_r ; i++)
{
assert(fread((void *)(conn->db->rows + i), sizeof(struct Address), 1, conn->file) == 1);
conn->db->rows[i].name = (char *)calloc(max_d, sizeof(char));
assert(fread((void *)(conn->db->rows[i].name), sizeof(char), max_d, conn->file) == max_d);
conn->db->rows[i].email = (char *)calloc(max_d, sizeof(char));
assert(fread((void *)(conn->db->rows[i].email), sizeof(char), max_d, conn->file) == max_d);
}
}

struct Connection *Database_open(const char *filename, char mode)
{
struct Connection *conn = malloc(sizeof(struct Connection));//Allocate memory for connection struct
if(!conn) die("Memory error", conn); //err report

conn->db = malloc(sizeof(struct Database)); //Alloc db mem
if(!conn->db) die("Memory error", conn); //err report

if(mode == 'c') {
conn->file = fopen(filename, "w"); //for create, fopen with write to create file
} else {
conn->file = fopen(filename, "r+"); //for others, read and write

if(conn->file) { //Ready to read database info from the selected file
Database_load(conn);
}
}

if(!conn->file) die("Failed to open the file", conn);

return conn;
}

void Database_close(struct Connection *conn)
{
if(conn) {
if(conn->file) fclose(conn->file);
if(conn->db)
{
for (int i = 0 ; i < conn->db->max_rows ; i++)
{
free((conn->db->rows + i)->name);
free((conn->db->rows + i)->email);
}
free(conn->db->rows);
free(conn->db);
}
free(conn);
}
}

void Database_write(struct Connection *conn)
{
int max_r = conn->db->max_rows, max_d = conn->db->max_data;
rewind(conn->file);
int rc = fwrite(conn->db, sizeof(struct Database), 1, conn->file);
if(rc != 1) die("Failed to write database.", conn);
//Load the db.dat as our formal format
for (int i = 0 ; i < max_r ; i++)
{
assert(fwrite((void *)(conn->db->rows + i), sizeof(struct Address), 1, conn->file) == 1);//Write an Address stuct
assert(fwrite((void *)(conn->db->rows[i].name), sizeof(char), max_d, conn->file) == max_d);
assert(fwrite((void *)(conn->db->rows[i].email), sizeof(char), max_d, conn->file) == max_d);
}
rc = fflush(conn->file);
if(rc == -1) die("Cannot flush database.", conn);
}

void Database_create(struct Connection *conn, __uint32_t max_r, __uint32_t max_d)
{
int i = 0;
conn->db->max_rows = max_r;
conn->db->max_data = max_d;
// printf("The max_r is %d and max_d is %d\n", max_r, max_d);
conn->db->rows = (struct Address *)calloc(max_r, sizeof(struct Address));
for(i = 0; i < max_r; i++) {
conn->db->rows[i].id = i;
conn->db->rows[i].set = 0;
conn->db->rows[i].name = (char *)calloc(max_d, sizeof(char));
conn->db->rows[i].email = (char *)calloc(max_d, sizeof(char));
// make a prototype to initialize it, name and email for pointers
// struct Address addr = {.id = i, .set = 0, .name = NULL, .email = NULL};
// then just assign it
// conn->db->rows[i] = addr;
// printf("The conn->db row id : %d is at address %p \n", i, conn->db->rows + i);
// printf("The address of row id %d the set is %p\n", i, &conn->db->rows[i].set);
}
}

void Database_set(struct Connection *conn, int id, const char *name, const char *email)
{
struct Address *addr = &conn->db->rows[id];
assert(conn->db != NULL);
assert(conn->db->rows != NULL);
assert((conn->db->rows + id) != NULL);
// printf("The conn->db row id : %d is at address %p \n", id, conn->db->rows + id);
assert(conn->db->rows[id].set == 0);
assert(addr != NULL); //We create valid row pointers and thus get the row[id] address
if(addr->set) die("Already set, delete it first", conn);
addr->set = 1;
// WARNING: bug, read the "How To Break It" and fix this
assert(addr->name != NULL);
char *res = strncpy(addr->name, name, conn->db->max_data);
*(res + conn->db->max_data - 1) = '\0'; //fix it
// demonstrate the strncpy bug
if(!res) die("Name copy failed", conn);
assert(addr->email != NULL);
res = strncpy(addr->email, email, conn->db->max_data);
*(res + conn->db->max_data - 1) = '\0';
if(!res) die("Email copy failed", conn);
}

void Database_get(struct Connection *conn, int id)
{
struct Address *addr = &conn->db->rows[id];

if(addr->set) {
Address_print(addr);
} else {
die("ID is not set", conn);
}
}

void Database_delete(struct Connection *conn, int id)
{
conn->db->rows[id].id = id;
conn->db->rows[id].set = 0;
memset((void *)conn->db->rows[id].name, 0, conn->db->max_data);
memset((void *)conn->db->rows[id].email, 0, conn->db->max_data);
}

void Database_list(struct Connection *conn)
{
int i = 0;
struct Database *db = conn->db;

for(i = 0; i < conn->db->max_rows; i++) {
struct Address *cur = &db->rows[i];

if(cur->set) {
Address_print(cur);
}
}
}

int main(int argc, char *argv[])
{
if(argc < 3) die("USAGE: ex17 <dbfile> <action> [action params]", NULL);

char *filename = argv[1];
char action = argv[2][0];
struct Connection *conn = Database_open(filename, action);
int id = 0;
__uint32_t max_r = 0, max_d = 0;
if(argc > 3) id = atoi(argv[3]);
if(id >= MAX_ROWS) die("There's not that many records.", conn);

switch(action) {
case 'c':// When its ./ex17 db.dat c [ARG:MAX_ROWS] [ARG:MAX_DATA]
if (argc != 5) die("Argument fault for creating a database! USAGE: ./ex17 <dbfile name> c MAX_ROWS MAX_DATA", conn);
max_r = atoi(argv[3]);
max_d = atoi(argv[4]);
Database_create(conn, max_r, max_d);
Database_write(conn);
break;

case 'g':
if(argc != 4) die("Need an id to get", conn);

Database_get(conn, id);
break;

case 's':
if(argc != 6) die("Need id, name, email to set", conn);

Database_set(conn, id, argv[4], argv[5]);
Database_write(conn);
break;

case 'd':
if(argc != 4) die("Need id to delete", conn);

Database_delete(conn, id);
Database_write(conn);
break;

case 'l':
Database_list(conn);
break;
default:
die("Invalid action, only: c=create, g=get, s=set, d=del, l=list", conn);
}

Database_close(conn);

return 0;
}

使用原有的测试样例来完成测试,如下所示:

![ex17_2](C:\Users\Junrong Huang\Desktop\ysyx-proj\pre-learning\ex_records\ex17_2.png)

  1. 我们实现的find如下代码所示:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
void Database_find(struct Connection *conn, char *name)
{
int max_r = conn->db->max_rows;//, max_d = conn->db->max_data;
for (int i = 0 ; i < max_r ; i++)
{
if(strcmp(conn->db->rows[i].name, name) == 0)
{
Address_print(conn->db->rows + i);
return;
}
}
printf("The name: %s is not in the database! \n", name);
return;
}

![ex17_3](C:\Users\Junrong Huang\Desktop\ysyx-proj\pre-learning\ex_records\ex17_3.png)

  1. 如何打包结构体?

    (1)首先,结构体需要按照i386 System V的ABI规约去进行对齐,整个结构体变量的对其方式需要按照其中最严格的成员来进行。

    (2)每个成员在满足对齐方式的前提下,取最小的可用位置作为成员在结构体中的偏移量,这可能会导致内部插空。

    (3)结构体大小应为对齐边界长度的整数倍,这可能会导致尾部插空。

    前两条规则是为了保证结构体中任意成员都能以对齐的方式访问。

    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
    //一些关于对齐的例子:
    struct SDT{
    int i;
    short si;
    double d;
    char c;
    }sa[10];
    /*
    该结构体会按照double来进行对齐,也即地址必定是8的倍数,所以结构体最终的大小应该是8的整数倍。
    4 + 2 + 8 + 1 = 15,按照边界对齐,那么结构体的大小最后就是24
    其内存安排如下所示:
    0 4 6 8 16 17 24
    | int | si |--| double | char |------|
    总体需要24个字节

    */
    //重新换一种结构体的组织形式,注意到si后面为了对齐double的留空可以用来插入一个char,这样重新安排之后:
    struct SDT{
    int i;
    short si;
    char c;
    double d;
    }sa[10];
    /*
    0 4 6 7 8 16
    | int | si | char |-| double |
    这样改之后结构体总体只需要16个字节
    */

    根据ls -l指令我们得到了指定文件db.dat的文件大小为2536b,根据我们的文件组织形式可以知道,含有1个database结构体,max_r个address结构体,max_r个长度为max_d的name和email字符串。

    考虑到对齐与64位机器上指针变量的大小,可知database结构体占16b,address结构体占24b,一个name/email字符串为30b。

    那么计算公式为16 + 24 * 30 + 30 * 1 * 2 * 30 = 2536b

    1. 额外添加一些信息即可,这样在database_load中需要额外malloc对应的空间。
    2. 我完成的自动测试脚本文件:
    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
    #!/bin/bash
    set -e

    echo "Creating database ..."
    ./ex17 db.dat c
    echo ""

    echo "Saving zed, frank, joe to database ..."
    ./ex17 db.dat s 1 zed zed@zedshaw.com
    ./ex17 db.dat s 2 frank frank@zedshaw.com
    ./ex17 db.dat s 3 joe joe@zedshaw.com
    echo ""

    echo "Printing all records ..."
    output=$(./ex17 db.dat l)
    ./ex17 db.dat l
    echo $output
    echo ""

    echo "Deleting record by id 3 ..."
    ./ex17 db.dat d 3
    echo ""

    echo "Printing all records ..."
    ./ex17 db.dat l
    echo ""

    echo "Getting record by id 2 ..."
    ./ex17 db.dat g 2
    1. 为了方便原有程序的编写,其实只需要将一开始在database_load中用malloc申请堆区内存的conn指针,改为指向一个全局变量即可。这个全局变量在database_close中不需要最后执行free(conn)了,因为并不是malloc在堆区上的内存,程序运行结束后其所占内存会随着程序结束自行释放。

    2. 实现的源代码和测试结果如下:

    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
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <stdbool.h>
    #define STK_SIZE 100

    struct stk{
    __uint32_t top; //mark the top of the stack
    char elem[STK_SIZE + 1]; //store the char elements in the stack
    };
    // 0 and the STK_SIZE are the floor and top! 1-STK_SIZE are valid!


    void warn(char *msg)
    {
    printf("%s \n", msg);
    exit(1);
    }

    void stack_push(struct stk *s, char ch)
    {
    if(s->top + 1 > STK_SIZE)
    {
    warn("The stack is overflow!");
    return;
    }
    s->top++;
    s->elem[s->top] = ch;
    printf("The letter %c is pushed in the stack \n", ch);
    }

    char stack_pop(struct stk *s)
    {
    if(s->top == 0)
    {
    warn("The stack is empty! Nothing to pop!");
    return '\0';
    }
    char ret = s->elem[s->top];
    s->top--;
    return ret;
    }

    char stack_top(struct stk *s)
    {
    if(s->top == 0)
    {
    warn("The stack is empty! Nothing at top!");
    return '\0';
    }
    else
    return s->elem[s->top];
    }

    bool stack_is_empty(struct stk *s)
    {
    return (s->top == 0);
    }

    struct stk *stack_init()
    {
    struct stk *mystk = malloc(sizeof(struct stk));
    mystk->top = 0;
    memset(mystk->elem, 0, sizeof(mystk->elem));
    return mystk;
    }
    int main()
    {
    struct stk *s = stack_init();
    stack_push(s, 'A');
    stack_push(s, 'B');
    stack_push(s, 'C');
    stack_push(s, 'D');
    if(stack_is_empty(s)) printf("Stack is empty!\n");
    else printf("Stack is not empty!\n");
    printf("Stack pop is %c\n", stack_pop(s));
    printf("Stack pop is %c\n", stack_pop(s));
    printf("Stack pop is %c\n", stack_pop(s));
    printf("Stack pop is %c\n", stack_pop(s));
    if(stack_is_empty(s)) printf("Stack is empty!\n");
    else printf("Stack is not empty!\n");
    return 0;
    }

    ![ex17_8](C:\Users\Junrong Huang\Desktop\ysyx-proj\pre-learning\ex_records\ex17_8.png)

Exercise 18 函数指针:

Notes:

函数实际上只是指向程序中某一个代码存在位置的指针,类似结构体指针、字符串和数组所代表的指针,我们也可以创建指向函数的指针。函数指针的主要用途是向其他函数传递“回调”,或者模拟类和对象。

函数指针的格式如下所示:

1
int (*POINTER_NAME)(int a, int b)

例如:

1
int (*compare_ab)(int a, int b)

上述格式定义完毕后,我们的指针变量名称为compare_db,而我们可以将它用作函数。这类似于指向数组的指针可以表示所指向的数组,指向函数的指针同样也可以表示对应的函数,只不过所用的是不同的名字。

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
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>

/** Our old friend die from ex17. */
void die(const char *message) //一个处理异常信息的终止函数
{
if(errno) {
perror(message);
} else {
printf("ERROR: %s\n", message);
}

exit(1);
}

// a typedef creates a fake type, in this
// case for a function pointer
typedef int (*compare_cb)(int a, int b); //这里将所有的。接受int a, int b作为参数且返回值为int的函数,用一个函数指针来指代了。这个函数指针的名字叫compare_cb。

/**
* A classic bubble sort function that uses the
* compare_cb to do the sorting.
*/
int *bubble_sort(int *numbers, int count, compare_cb cmp) //这个函数是正常的函数,并不是函数指针。只不过是因为这个函数的返回值是一个指向int类型的指针罢了。如果函数名和*号没有一起被包裹的话,那么是实际上定义的就是一个返回值为指针类型的函数。这里bubble_sort的结果是一个int数组,显然就返回了一个指向这个数组首的int类型的指针。
{
int temp = 0;
int i = 0;
int j = 0;
int *target = malloc(count * sizeof(int)); //这里,开辟一块用来做冒泡泡排序的辅助空间。

if(!target) die("Memory error.");

memcpy(target, numbers, count * sizeof(int)); //拷贝一份原有数据到辅助空间

for(i = 0; i < count; i++) { //循环count轮,每轮作比较
for(j = 0; j < count - 1; j++) {
if(cmp(target[j], target[j+1]) > 0) { //如果按照回调函数符合交互需求,那么就交换。
temp = target[j+1];
target[j+1] = target[j];
target[j] = temp;
}
}
}

return target;
}
//下面是三种不同的回调函数。
int sorted_order(int a, int b)
{
return a - b;
}

int reverse_order(int a, int b)
{
return b - a;
}

int strange_order(int a, int b)
{
if(a == 0 || b == 0) {
return 0;
} else {
return a % b;
}
}

/**
* Used to test that we are sorting things correctly
* by doing the sort and printing it out.
*/
void test_sorting(int *numbers, int count, compare_cb cmp)
{
int i = 0;
int *sorted = bubble_sort(numbers, count, cmp);

if(!sorted) die("Failed to sort as requested.");

for(i = 0; i < count; i++) {
printf("%d ", sorted[i]);
}
printf("\n");

free(sorted);
}


int main(int argc, char *argv[])
{
if(argc < 2) die("USAGE: ex18 4 3 1 5 6");

int count = argc - 1;
int i = 0;
char **inputs = argv + 1;

int *numbers = malloc(count * sizeof(int));
if(!numbers) die("Memory error.");

for(i = 0; i < count; i++) {
numbers[i] = atoi(inputs[i]);
}
//这里向test_sorting传入不同的回调函数,分别为sorted_order...
test_sorting(numbers, count, sorted_order);
test_sorting(numbers, count, reverse_order);
test_sorting(numbers, count, strange_order);

free(numbers);

return 0;
}

补充知识:

typedef关键字可以用来给一个类型重新起一个新的名字。例如

1
2
typedef unsigned char BYTE;
BYTE b1, b2; 就和 unsigned char b1, b2; 等价了。

typedef和#define预定义宏的区别:typedef只能对变量类型做符号上等价的名称替代,而#define预定义宏也可以对变量来进行相应的名称替代;typedef是由编译器来进行转译处理的,而#define预处理宏是由预处理器来做处理的。

注意,函数指针如果定义成typedef int (*compare_ab)(int a, int b),那么作为类型定义的compare_ab cmp调用时可以直接cmp(x, y)来实现两个元素的比较。

附加题:

1.

![ex18_1](C:\Users\Junrong Huang\Desktop\ysyx-proj\pre-learning\ex_records\ex18_1.png)

使用objdump工具来分析ex18的二进制源码,并以十六进制的形式来解读。

![ex18_11](C:\Users\Junrong Huang\Desktop\ysyx-proj\pre-learning\ex_records\ex18_11.png)

根据打印出来的十六进制内容可知,指针转换后打印的内容分别为三个函数具体的汇编源码。

2.

![ex18_2_1](C:\Users\Junrong Huang\Desktop\ysyx-proj\pre-learning\ex_records\ex18_2_1.png)

![ex18_2_2](C:\Users\Junrong Huang\Desktop\ysyx-proj\pre-learning\ex_records\ex18_2_2.png)

修改字符串,使用工具hexedit,对一开始的保存信息做一些修改。即USAGE: ex18 4 3 1 5 6

3.

![ex18_3](C:\Users\Junrong Huang\Desktop\ysyx-proj\pre-learning\ex_records\ex18_3.png)

传错误的函数,编译器会报错,指出我们传入的指针类型与compare_ab时不同的。也即传入了一个void ( * )(const char *)的函数指针,与定义里的int ( * )(int a, int b)不同。

4.

![ex18_4](C:\Users\Junrong Huang\Desktop\ysyx-proj\pre-learning\ex_records\ex18_4.png)

因为传入的是NULL,那么在访问地址解析函数的过程中就会发生越界现象。报错segmentation fault

5.

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
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>

/** Our old friend die from ex17. */
void die(const char *message)
{
if(errno) {
perror(message);
} else {
printf("ERROR: %s\n", message);
}

exit(1);
}

// a typedef creates a fake type, in this
// case for a function pointer
typedef int (*compare_cb)(int a, int b);
typedef int *(*sort_algorithm)(int *numbers, int count, compare_cb cmp);
/**
* A classic bubble sort function that uses the
* compare_cb to do the sorting.
*/
int *bubble_sort(int *numbers, int count, compare_cb cmp)
{
int temp = 0;
int i = 0;
int j = 0;
int *target = malloc(count * sizeof(int));

if(!target) die("Memory error.");

memcpy(target, numbers, count * sizeof(int));

for(i = 0; i < count; i++) {
for(j = 0; j < count - 1; j++) {
if(cmp(target[j], target[j+1]) > 0) {
temp = target[j+1];
target[j+1] = target[j];
target[j] = temp;
}
}
}

return target;
}

int sorted_order(int a, int b)
{
return a - b;
}

int reverse_order(int a, int b)
{
return b - a;
}

int strange_order(int a, int b)
{
if(a == 0 || b == 0) {
return 0;
} else {
return a % b;
}
}
void swap(int *x, int *y)
{
int t = *x;
*x = *y;
*y = t;
}

int *insert_sort(int *numbers, int count, compare_cb cmp)
{
int *target = malloc(count * sizeof(int));
memcpy(target, numbers, count * sizeof(int));
for (int i = 1 ; i < count ; i++)
{
for (int j = 0 ; j < i ; j++)
{
if(cmp(target[j],target[i]) >= 0)
{
for (int k = i ; k >= j + 1 ; k--)
swap(target + k, target + k - 1);
break;
}
}
}
return target;
}



/**
* Used to test that we are sorting things correctly
* by doing the sort and printing it out.
*/
void test_sorting(int *numbers, int count,sort_algorithm sort, compare_cb cmp)
{
int i = 0;
int *sorted = sort(numbers, count, cmp);

if(!sorted) die("Failed to sort as requested.");

for(i = 0; i < count; i++) {
printf("%d ", sorted[i]);
}
printf("\n");

free(sorted);

unsigned char *data = (unsigned char *)cmp;

for(i = 0; i < 25; i++) {
printf("%02x:", data[i]);
}

printf("\n");


}



int main(int argc, char *argv[])
{
if(argc < 2) die("USAGE: ex18 4 3 1 5 6");

int count = argc - 1;
int i = 0;
char **inputs = argv + 1;

int *numbers = malloc(count * sizeof(int));
if(!numbers) die("Memory error.");

for(i = 0; i < count; i++) {
numbers[i] = atoi(inputs[i]);
}
printf("The bubble sort results:\n");
test_sorting(numbers, count, bubble_sort, sorted_order);
test_sorting(numbers, count, bubble_sort, reverse_order);
test_sorting(numbers, count, bubble_sort, strange_order);
printf("The insert sort results:\n");
test_sorting(numbers, count, insert_sort, sorted_order);
test_sorting(numbers, count, insert_sort, reverse_order);
test_sorting(numbers, count, insert_sort, strange_order);


free(numbers);

return 0;
}

额外编写了插入排序的方式,并修改test_sorting使其接受不同的排序回调函数和排序规则函数。

ex19 预处理与OOP

  1. cpp - The C Preprocessor,c预处理器。c预处理器,是一种宏预处理器,会被C编译器自动使用来转换程序。

  2. pipe: 管道是Linux通信机制中很有效的一个工具,对于 command1 | command2,管道通常存在的意义就是,将command1输出的内容重定向为command2输入的内容。 https://fedoramagazine.org/command-line-quick-tips-using-pipes-to-connect-tools/

  3. grep: 进行模式匹配的查找

  4. Linux less 工具,可以展示文件的内容。 https://linuxize.com/post/less-command-in-linux/

  5. 作者在设计每个类(如Room,Monster和Map)的时候,都将对象Object放在了结构体的开头,这样的话,NEW里面传入了每个申请结构体的sizeof申请一块空间之后,Object_new返回的指针转换为Object*之后也一定指向了其原型结构体的指针。

ex20 debug宏

  1. 出现在dbg.h中的有意思的宏:
1
2
3
__FILE__ //有的编译器并不支持,但是gcc支持该宏,用来获取当前文件的路径,在我们需要生成日志文件的时候尤其有用。一般调用的时候会返回一个const char* file.
__LINE__ //可以用来获取在源文件中的当前代码行号,作为一个integer出现,调用结果会返回一个int类型的行号。
__FUNCTION__ //该宏可以用来返回当前函数,在调用的时候,会返回一个const char* 的字符串,代表当前调用所在的函数名。
  1. strerror(errornum)函数可以接受一个错误码errno,然后返回一个解释该错误码的字符串指针。所以clean_errno宏可以用于获取errno的可读的版本,并作为一个参量传入fprintf中
  2. 预处理器在解析宏的过程:
1
2
3
4
5
6
7
8
9
##__VA_ARGS__ 是传入的剩余参数,对应...部分
//以下是debug宏展开的一个过程
对于宏:
#define log_err(M, ...) fprintf(stderr, "[ERROR] (%s:%d: errno:%s) " M "\n" , __FILE__, __LINE__, clear_errno(), ##__VA_ARGS__)
实际上所做的是,打印出来文件__FILE__的第__LINE__行的报错信息为clear_errno()返回的解析内容,并给出自定义反馈内容。以格式M为控制字符串,...为自定义需要打印的参数。
//代码中写出的debug内容宏为:
log_err("The pointer is empty! The age is %d and name is %s", age, name);
//最后cpp预处理器得到的宏展开结果就是:
fprintf(stderr, "[ERROR] (%s:%d: errno:%s) The pointer is empty! The age is %d and name is %s \n", __FILE__, __LINE__, clear_errno(), age, name);

更进一步,我们使用宏来做debug而不是函数,因为我们debug的过程需要具体的文件名、行号等信息来辅助我们debug。

  1. Makefile中,我们可以向CFLAGS传参-D NDEBUG来实现宏的引入定义。这样即使源文件中没有定义宏NDEBUG,我们也可以达到有NDEBUG宏的效果。
  2. 附加题中让我们额外添加调用函数的宏,只需要在格式控制字符串中额外添加一个输出_FUNCTION _的部分即可。

ex21 类型、限定

  1. int虽然在32或64位环境下为32位,但是它并不应该被是做是与平台无关的。如果需要用到与平台无关的定长整数,请使用int(n)_t。
  2. 符号修饰符signed/unsigned只对char和 *** int有效, 后者默认为signed,而char根据具体实现可以默认为signed。在需要做memcpy等的时候最好还是转换为unsigned来做。
  3. 类型限定符volatile,表示会做最坏的打算,编译器不会对他做让任何优化。
  4. stdint.h中会为定长的整数类型做一些typedef,同时也有一些用户这些类型的宏。如int32_t、uint64_t等等。这些数据的最值可以用宏INT(N)_MAX来获得,如INT16_MAX可以得到int16 _ t的最大值。UINT(N) _ MAX同理
  5. intptr_t:足够存放指针的符号整数 uintptr_t: 足够存放指针的无符号整数
  6. size_t:是无符号整型数据,其命名原有是用来代表对象占有的字节数,作为sizeof操作的返回值,显然是无符号的。

ex22:栈、作用域和全局

  1. extern关键字:告诉编译器,这个变量已存在,但是它在其他的“外部区域”中。通常它出现的情况是一个.c文件要用到另一个.c文件所定义的变量。这种情况下,我们可以说ex22.c中的THE_SIZE变量能被ex22_main.c访问到
  2. static(文件)关键词的意思是指这个变量只能在当前的.c文件中使用,程序的其他部分不可访问。编译生成的ELF文件中会被存储到.bss段。
  3. static(函数) 如何使用static在函数中声明变量,那么它和文件中的static定义类似,但是只能够在该函数中访问。他是一种创建某个函数的持续状态的方法。

ex23: duff device

https://mthli.xyz/duff-device/

ex24: 输入输出和文件

  1. 在读取的细节上,fscanf和fgets是由区别的。scanf在读取之前往往不知道输入缓冲区有多大,同理函数gets也是一个具有该问题的函数·。而fgets可以解决该问题,不会读取过多的呢欸容。

ex25: 变参函数