24 / 07 / 13

「一生一芯」Learn C the hard way - Ex 17

练习 17:堆和栈的内存分配

#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) { 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) { int rc = fread(conn->db, sizeof(struct Database), 1, conn->file); if(rc != 1) die("Failed to load database."); } struct Connection *Database_open(const char *filename, char mode) { struct Connection *conn = malloc(sizeof(struct Connection)); if(!conn) die("Memory error"); conn->db = malloc(sizeof(struct Database)); if(!conn->db) die("Memory error"); 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); int rc = fwrite(conn->db, sizeof(struct Database), 1, conn->file); if(rc != 1) die("Failed to write database."); 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}; 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; }

结构体之间的关系

+-----------------+ +--------------------+ +--------------------+ | struct Address |<--->| struct Database |<--->| struct Connection | +-----------------+ +--------------------+ +--------------------+ | id | | rows[MAX_ROWS] | | file | | set | | - Address[0] | | db | | name[MAX_DATA] | | - Address[1] | | | | email[MAX_DATA] | | - ... | | | +-----------------+ | - Address[99] | +--------------------+ +--------------------+
  • Address 结构体表示数据库中的一条记录。

    • id 是记录的唯一标识符。

    • set 表示该记录是否已经设置(即是否有效)。

    • nameemail 是记录的具体数据,分别存储名字和邮箱。

  • Database 结构体表示整个数据库。

    • 它包含一个 Address 数组 rows,每个元素代表一条记录。

    • MAX_ROWS 定义了数据库可以包含的最大记录数(100条)。

  • Connection 结构体表示与数据库文件的连接。

    • file 是一个文件指针,指向数据库文件。

    • db 是一个指向 Database 结构体的指针,表示内存中的数据库内容。

一些不太熟悉的函数

  • errno 是一个全局变量,表示最近一次函数调用失败时的错误码。

  • perror 用于打印描述错误码的错误信息。

  • malloc:分配指定大小的内存块,并返回指向该内存块的指针。

  • free:释放由 malloc 分配的内存块。

  • exit:终止程序执行,并返回一个状态码给操作系统。

  • atoi:将字符串转换为整数。

    • 它会跳过字符串前面的所有空白字符(如空格、制表符等)。

    • 然后它会尽可能多地转换数字字符,直到遇到非数字字符或字符串末尾为止。

    • 如果字符串不包含任何可转换的数字字符,那么 atoi 会返回 0

    • Example:123abc456 → 123.

  • fopen:用于打开文件,并根据不同的模式进行读写操作。

  • fclose:用于关闭打开的文件。

  • fread:用于从文件中读取数据到内存。

  • fwrite:用于将内存中的数据写入文件。

  • rewind:用于将文件指针移到文件开头。

  • fflush:用于刷新文件流的输出缓冲区,确保数据写入文件。

strncpy设计缺陷修复:

addr->name[MAX_DATA - 1] = '\0';

附加题 1

die函数需要接收conn变量作为参数,以便执行清理并关闭它。

附加题 2

修改代码,使其接收参数作为MAX_DATA和MAX_ROWS,将它们储存在Database结构体中,并且将它们写到文件。这样就可以创建任意大小的数据库。

#include <stdio.h> #include <assert.h> #include <stdlib.h> #include <errno.h> #include <string.h> struct Address { int id; int set; char *name; char *email; }; struct Database { int max_data; int max_rows; struct Address *rows; }; struct Connection { FILE *file; struct Database *db; }; void Database_close(struct Connection *conn) { if(conn) { if(conn->file) fclose(conn->file); if(conn->db) free(conn->db); free(conn); } } void die(struct Connection *conn, const char *message) { if (errno) { perror(message); } else { printf("ERROR: %s\n", message); } Database_close(conn); 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) { int rc = fread(conn->db, sizeof(struct Database), 1, conn->file); if (rc != 1) die(conn, "Failed to load database header."); } struct Connection *Database_open(const char *filename, char mode, int max_data, int max_rows) { struct Connection *conn = malloc(sizeof(struct Connection)); if (!conn) die(NULL, "Memory error"); conn->db = malloc(sizeof(struct Database)); if (!conn->db) die(conn, "Memory error"); if (mode == 'c') { conn->file = fopen(filename, "w"); } else { conn->file = fopen(filename, "r+"); if (conn->file) { Database_load(conn); } } if (!conn->file) die(conn, "Failed to open the file"); return conn; } void Database_write(struct Connection *conn) { rewind(conn->file); int rc = fwrite(conn->db, sizeof(struct Database), 1, conn->file); if (rc != 1) die(conn, "Failed to write database header."); rc = fflush(conn->file); if (rc == -1) die(conn, "Cannot flush database."); } void Database_create(struct Connection *conn) { for (int i = 0; i < conn->db->max_rows; i++) { struct Address addr = {.id = i, .set = 0}; conn->db->rows[i] = addr; } printf("SUCCESS!\n"); } 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(conn, "Already set, delete it first"); addr->set = 1; char *res_name = strncpy(addr->name, name, conn->db->max_data); if (!res_name) die(conn, "Name copy failed"); char *res_email = strncpy(addr->email, email, conn->db->max_data); if (!res_email) die(conn, "Email copy failed"); if (res_name && res_email) printf("SUCCESS!\n"); } void Database_get(struct Connection *conn, int id) { struct Address *addr = &conn->db->rows[id]; if (addr->set) { Address_print(addr); } else { die(conn, "ID is not set"); } } void Database_delete(struct Connection *conn, int id) { struct Address addr = {.id = id, .set = 0}; conn->db->rows[id] = addr; printf("SUCCESS!\n"); } void Database_list(struct Connection *conn) { struct Database *db = conn->db; for (int i = 0; i < 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(NULL, "USAGE: ex17 <dbfile> <action> [action params]"); char *filename = argv[1]; char action = argv[2][0]; struct Connection *conn = NULL; int id = 0; int max_data = 0; int max_rows = 0; if (action == 'c') { if (argc != 5) die(NULL, "Need max_data and max_rows to create"); max_data = atoi(argv[3]); max_rows = atoi(argv[4]); conn = Database_open(filename, action, max_data, max_rows); Database_create(conn); Database_write(conn); } else { conn = Database_open(filename, action, max_data, max_rows); if (argc > 3) id = atoi(argv[3]); //if (id >= conn->db->max_rows) die(conn, "There's not that many records."); switch (action) { case 'g': if (argc != 4) die(conn, "Need an id to get"); Database_get(conn, id); break; case 's': if (argc != 6) die(conn, "Need id, name, email to set"); Database_set(conn, id, argv[4], argv[5]); Database_write(conn); break; case 'd': if (argc != 4) die(conn, "Need id to delete"); Database_delete(conn, id); Database_write(conn); break; case 'l': Database_list(conn); break; default: die(conn, "Invalid action, only: c=create, g=get, s=set, d=del, l=list"); } } Database_close(conn); return 0; }

内存溢出原因:struct database 的字节数不能被正常获取。

解决方法:不一次性 load 整个结构体,先读取参数,再读取数组。

#include <stdio.h> #include <assert.h> #include <stdlib.h> #include <errno.h> #include <string.h> struct Address { int id; int set; char *name; char *email; }; struct Database { int max_data; int max_rows; struct Address **rows; }; struct Connection { FILE *file; struct Database *db; }; void Database_close(struct Connection *conn) { if (conn) { if (conn->db && conn->db->rows) { for (int i = 0; i < conn->db->max_rows; i++) { struct Address *cur = conn->db->rows[i]; free(cur); } } if (conn->file) fclose(conn->file); if (conn->db) free(conn->db); free(conn); printf("Database close : success\n"); } } void die(struct Connection *conn, const char *message) { if (errno) { perror(message); } else { printf("ERROR: %s\n", message); } Database_close(conn); 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) { assert(conn->db && conn->file); if (!(conn->db && conn->file)) die(conn, "Database load : Invalid Connection info"); if (fread(&conn->db->max_data, sizeof(conn->db->max_data), 1, conn->file) != 1) die(conn, "Database load : Couldn't read MAX_DATA"); if (fread(&conn->db->max_rows, sizeof(conn->db->max_rows), 1, conn->file) != 1) die(conn, "Database load : Couldn't read MAX_ROWS"); conn->db->rows = (struct Address **)malloc(sizeof(struct Address *) * conn->db->max_rows); assert(conn->db->rows); if (!(conn->db->rows)) die(conn, "Database_load : Could not read MAX_ROWS Address structures"); for (int i = 0; i < conn->db->max_rows; i++) { conn->db->rows[i] = (struct Address *)malloc(sizeof(struct Address)); struct Address *row = conn->db->rows[i]; if (fread(&row->id, sizeof(row->id), 1, conn->file) != 1) die(conn, "Database load : Could not read Address::id"); if (fread(&row->set, sizeof(row->set), 1, conn->file) != 1) die(conn, "Database load : Couldn't read Address::set"); row->name = malloc(conn->db->max_data * sizeof(*row->name)); row->email = malloc(conn->db->max_data * sizeof(*row->email)); assert(row->email && row->name); if (!(row->name && row->email)) die(conn, "Database load : Failed to Allocate Address strings"); if (fread(row->name, conn->db->max_data * sizeof(*row->name), 1, conn->file) != 1) die(conn, "Database load : Failed to read Address::name"); if (fread(row->email, conn->db->max_data * sizeof(*row->email), 1, conn->file) != 1) die(conn, "Database load : Failed to read Address::email"); } printf("Database load : success\n"); } struct Connection *Database_open(const char *filename, char mode, int max_data, int max_rows) { struct Connection *conn = malloc(sizeof(struct Connection)); if (!conn) die(NULL, "Memory error"); conn->db = malloc(sizeof(struct Database)); if (!conn->db) die(conn, "Memory error"); if (mode == 'c') { conn->file = fopen(filename, "w"); } else { conn->file = fopen(filename, "r+"); if (conn->file) { Database_load(conn); } } if (!conn->file) die(conn, "Failed to open the file"); else printf("Database open : success\n"); return conn; } void Database_write(struct Connection *conn) { rewind(conn->file); if (fwrite(&conn->db->max_data, sizeof(conn->db->max_data), 1, conn->file) != 1) die(conn, "Database write : Failed to write MAX_DATA"); if (fwrite(&conn->db->max_rows, sizeof(conn->db->max_rows), 1, conn->file) != 1) die(conn, "Database write : Failed to write MAX_ROWS"); for (int i = 0; i < conn->db->max_rows; i++) { struct Address *row = conn->db->rows[i]; if (fwrite(&row->id, sizeof(row->id), 1, conn->file) != 1) die(conn, "Database write : Failed to write Address(id)"); if (fwrite(&row->set, sizeof(row->set), 1, conn->file) != 1) die(conn, "Database write : Failed to write Address(set)"); if (fwrite(row->name, sizeof(char) * conn->db->max_data, 1, conn->file) != 1) die(conn, "Database write : Failed to write Address(name)"); if (fwrite(row->email, sizeof(char) * conn->db->max_data, 1, conn->file) != 1) die(conn, "Database write : Failed to write Address(email)"); } if (fflush(conn->file) == -1) die(conn, "Database_write : Cannot flush database."); else printf("Database write : success\n"); } void Database_create(struct Connection *conn, int max_data, int max_rows) { conn->db->max_data = max_data; conn->db->max_rows = max_rows; conn->db->rows = (struct Address **)malloc(sizeof(struct Address *) * max_rows); for (int i = 0; i < max_rows; i++) { conn->db->rows[i] = (struct Address *)malloc(sizeof(struct Address)); conn->db->rows[i]->id = i; conn->db->rows[i]->set = 0; conn->db->rows[i]->name = (char *)malloc(conn->db->max_data); conn->db->rows[i]->name = (char *)memset(conn->db->rows[i]->name, ' ', conn->db->max_data); conn->db->rows[i]->email = (char *)malloc(conn->db->max_data); conn->db->rows[i]->email = (char *)memset(conn->db->rows[i]->email, ' ', conn->db->max_data); } printf("Database Create : success\n"); } void Database_set(struct Connection *conn, int id, const char *name, const char *email) { if (!(conn && conn->db && conn->db->rows && conn->db->rows[id])) die(conn, "Wrong database structure"); struct Address *addr = conn->db->rows[id]; int MAX_DATA = conn->db->max_data; if (addr->set == 1) die(conn, "Already set, delete it first"); addr->set = 1; addr->name = malloc(sizeof(char) * MAX_DATA); addr->email = malloc(sizeof(char) * MAX_DATA); char *res = strncpy(addr->name, name, MAX_DATA); addr->name[MAX_DATA - 1] = '\0'; if (!res) die(conn, "Name copy failed"); res = strncpy(addr->email, email, MAX_DATA); addr->name[MAX_DATA - 1] = '\0'; if (!res) die(conn, "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(conn, "ID is not set"); } } void Database_delete(struct Connection *conn, int id) { struct Address addr = {.id = id, .set = 0}; conn->db->rows[id] = &addr; printf("SUCCESS!\n"); } void Database_list(struct Connection *conn) { struct Database *db = conn->db; for (int i = 0; i < 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(NULL, "USAGE: ex17 <dbfile> <action> [action params]"); char *filename = argv[1]; char action = argv[2][0]; struct Connection *conn = NULL; int id = 0; int max_data = 0; int max_rows = 0; if (action == 'c') { if (argc != 5) die(NULL, "Need max_data and max_rows to create"); max_data = atoi(argv[3]); max_rows = atoi(argv[4]); conn = Database_open(filename, action, max_data, max_rows); Database_create(conn, max_data, max_rows); Database_write(conn); } else { conn = Database_open(filename, action, 0, 0); if (argc > 3) id = atoi(argv[3]) - 1; if (id > conn->db->max_rows) die(conn, "There's not that many records."); switch (action) { case 'g': if (argc != 4) die(conn, "Need an id to get"); Database_get(conn, id); break; case 's': if (argc != 6) die(conn, "Need id, name, email to set"); Database_set(conn, id, argv[4], argv[5]); Database_write(conn); break; case 'd': if (argc != 4) die(conn, "Need id to delete"); Database_delete(conn, id); Database_write(conn); break; case 'l': Database_list(conn); break; default: die(conn, "Invalid action, only: c=create, g=get, s=set, d=del, l=list"); } } Database_close(conn); return 0; }

附加题 3

向数据库添加更多操作,比如find。

#include <stdio.h> #include <assert.h> #include <stdlib.h> #include <errno.h> #include <string.h> struct Address { int id; int set; char *name; char *email; }; struct Database { int max_data; int max_rows; struct Address **rows; }; struct Connection { FILE *file; struct Database *db; }; void Database_close(struct Connection *conn) { if (conn) { if (conn->db && conn->db->rows) { for (int i = 0; i < conn->db->max_rows; i++) { struct Address *cur = conn->db->rows[i]; free(cur); } } if (conn->file) fclose(conn->file); if (conn->db) free(conn->db); free(conn); printf("Database close : success\n"); } } void die(struct Connection *conn, const char *message) { if (errno) { perror(message); } else { printf("ERROR: %s\n", message); } Database_close(conn); exit(1); } void Address_print(struct Address *addr) { printf("%d %s %s\n", addr->id + 1, addr->name, addr->email); } void Database_load(struct Connection *conn) { assert(conn->db && conn->file); if (!(conn->db && conn->file)) die(conn, "Database load : Invalid Connection info"); if (fread(&conn->db->max_data, sizeof(conn->db->max_data), 1, conn->file) != 1) die(conn, "Database load : Couldn't read MAX_DATA"); if (fread(&conn->db->max_rows, sizeof(conn->db->max_rows), 1, conn->file) != 1) die(conn, "Database load : Couldn't read MAX_ROWS"); conn->db->rows = (struct Address **)malloc(sizeof(struct Address *) * conn->db->max_rows); assert(conn->db->rows); if (!(conn->db->rows)) die(conn, "Database_load : Could not read MAX_ROWS Address structures"); for (int i = 0; i < conn->db->max_rows; i++) { conn->db->rows[i] = (struct Address *)malloc(sizeof(struct Address)); struct Address *row = conn->db->rows[i]; if (fread(&row->id, sizeof(row->id), 1, conn->file) != 1) die(conn, "Database load : Could not read Address::id"); if (fread(&row->set, sizeof(row->set), 1, conn->file) != 1) die(conn, "Database load : Couldn't read Address::set"); row->name = malloc(conn->db->max_data * sizeof(*row->name)); row->email = malloc(conn->db->max_data * sizeof(*row->email)); assert(row->email && row->name); if (!(row->name && row->email)) die(conn, "Database load : Failed to Allocate Address strings"); if (fread(row->name, conn->db->max_data * sizeof(*row->name), 1, conn->file) != 1) die(conn, "Database load : Failed to read Address::name"); if (fread(row->email, conn->db->max_data * sizeof(*row->email), 1, conn->file) != 1) die(conn, "Database load : Failed to read Address::email"); } printf("Database load : success\n"); } struct Connection *Database_open(const char *filename, char mode, int max_data, int max_rows) { struct Connection *conn = malloc(sizeof(struct Connection)); if (!conn) die(NULL, "Memory error"); conn->db = malloc(sizeof(struct Database)); if (!conn->db) die(conn, "Memory error"); if (mode == 'c') { conn->file = fopen(filename, "w"); } else { conn->file = fopen(filename, "r+"); if (conn->file) { Database_load(conn); } } if (!conn->file) die(conn, "Failed to open the file"); else printf("Database open : success\n"); return conn; } void Database_write(struct Connection *conn) { rewind(conn->file); if (fwrite(&conn->db->max_data, sizeof(conn->db->max_data), 1, conn->file) != 1) die(conn, "Database write : Failed to write MAX_DATA"); if (fwrite(&conn->db->max_rows, sizeof(conn->db->max_rows), 1, conn->file) != 1) die(conn, "Database write : Failed to write MAX_ROWS"); for (int i = 0; i < conn->db->max_rows; i++) { struct Address *row = conn->db->rows[i]; if (fwrite(&row->id, sizeof(row->id), 1, conn->file) != 1) die(conn, "Database write : Failed to write Address(id)"); if (fwrite(&row->set, sizeof(row->set), 1, conn->file) != 1) die(conn, "Database write : Failed to write Address(set)"); if (fwrite(row->name, sizeof(char) * conn->db->max_data, 1, conn->file) != 1) die(conn, "Database write : Failed to write Address(name)"); if (fwrite(row->email, sizeof(char) * conn->db->max_data, 1, conn->file) != 1) die(conn, "Database write : Failed to write Address(email)"); } if (fflush(conn->file) == -1) die(conn, "Database_write : Cannot flush database."); else printf("Database write : success\n"); } void Database_create(struct Connection *conn, int max_data, int max_rows) { conn->db->max_data = max_data; conn->db->max_rows = max_rows; conn->db->rows = (struct Address **)malloc(sizeof(struct Address *) * max_rows); for (int i = 0; i < max_rows; i++) { conn->db->rows[i] = (struct Address *)malloc(sizeof(struct Address)); conn->db->rows[i]->id = i; conn->db->rows[i]->set = 0; conn->db->rows[i]->name = (char *)malloc(conn->db->max_data); conn->db->rows[i]->name = (char *)memset(conn->db->rows[i]->name, ' ', conn->db->max_data); conn->db->rows[i]->email = (char *)malloc(conn->db->max_data); conn->db->rows[i]->email = (char *)memset(conn->db->rows[i]->email, ' ', conn->db->max_data); } printf("Database Create : success\n"); } void Database_set(struct Connection *conn, int id, const char *name, const char *email) { if (!(conn && conn->db && conn->db->rows && conn->db->rows[id])) die(conn, "Wrong database structure"); struct Address *addr = conn->db->rows[id]; int MAX_DATA = conn->db->max_data; if (addr->set == 1) die(conn, "Already set, delete it first"); addr->set = 1; addr->name = malloc(sizeof(char) * MAX_DATA); addr->email = malloc(sizeof(char) * MAX_DATA); char *res = strncpy(addr->name, name, MAX_DATA); addr->name[MAX_DATA - 1] = '\0'; if (!res) die(conn, "Name copy failed"); res = strncpy(addr->email, email, MAX_DATA); addr->name[MAX_DATA - 1] = '\0'; if (!res) die(conn, "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(conn, "ID is not set"); } } void Database_delete(struct Connection *conn, int id) { struct Address addr = {.id = id, .set = 0}; conn->db->rows[id] = &addr; printf("SUCCESS!\n"); } void Database_list(struct Connection *conn) { struct Database *db = conn->db; for (int i = 0; i < db->max_rows; i++) { struct Address *cur = db->rows[i]; if (cur->set) { Address_print(cur); } } } void Database_find(struct Connection *conn, const char *type, const char *aim) { struct Database *db = conn->db; if (strcmp(type, "name") == 0) { for (int i = 0; i < db->max_rows; i++) { struct Address *cur = db->rows[i]; if (cur->set) { if (strcmp(aim, cur->name) == 0) { Address_print(cur); } } } } else if (strcmp(type, "email") == 0) { for (int i = 0; i < db->max_rows; i++) { struct Address *cur = db->rows[i]; if (cur->set) { if (strcmp(aim, cur->email) == 0) { Address_print(cur); } } } } else { die(conn, "Wrong type"); } } int main(int argc, char *argv[]) { if (argc < 3) die(NULL, "USAGE: ex17 <dbfile> <action> [action params]"); char *filename = argv[1]; char action = argv[2][0]; struct Connection *conn = NULL; int id = 0; int max_data = 0; int max_rows = 0; if (action == 'c') { if (argc != 5) die(NULL, "Need max_data and max_rows to create"); max_data = atoi(argv[3]); max_rows = atoi(argv[4]); conn = Database_open(filename, action, max_data, max_rows); Database_create(conn, max_data, max_rows); Database_write(conn); } else { conn = Database_open(filename, action, 0, 0); if (argc > 3) id = atoi(argv[3]) - 1; if (id > conn->db->max_rows - 1) die(conn, "There's not that many records."); switch (action) { case 'g': if (argc != 4) die(conn, "Need an id to get"); Database_get(conn, id); break; case 's': if (argc != 6) die(conn, "Need id, name, email to set"); Database_set(conn, id, argv[4], argv[5]); Database_write(conn); break; case 'd': if (argc != 4) die(conn, "Need id to delete"); Database_delete(conn, id); Database_write(conn); break; case 'l': Database_list(conn); break; case 'f': Database_find(conn, argv[3], argv[4]); break; default: die(conn, "Invalid action, only: c=create, g=get, s=set, d=del, l=list"); } } Database_close(conn); return 0; }

附加题 4

查询C如何打包结构体,并且试着弄清楚为什么你的文件是相应的大小。看看你是否可以计算出结构体添加一些字段之后的新大小。

+-----------------+ +--------------------+ +--------------------+ | struct Address |<--->| struct Database |<--->| struct Connection | +-----------------+ +--------------------+ +--------------------+ | id | | MAX_DATA | | file | | set | | MAX_ROWS | | db | | name[MAX_DATA] | | rows[MAX_ROWS] | | | | email[MAX_DATA] | | - Address[0] | | | +-----------------+ | - Address[...] | +--------------------+ +--------------------+ [4bit + 4bit + MAX_DATA + MAX_DATA] * MAX_ROWS + 4bit +4bit

附加题 5

向Address添加一些字段,使它们可被搜索。

#include <stdio.h> #include <assert.h> #include <stdlib.h> #include <errno.h> #include <string.h> struct Address { int id; int set; char *name; char *email; char *phone; // 新增字段 }; struct Database { int max_data; int max_rows; struct Address **rows; }; struct Connection { FILE *file; struct Database *db; }; void Database_close(struct Connection *conn) { if (conn) { if (conn->db && conn->db->rows) { for (int i = 0; i < conn->db->max_rows; i++) { struct Address *cur = conn->db->rows[i]; free(cur); } } if (conn->file) fclose(conn->file); if (conn->db) free(conn->db); free(conn); printf("Database close : success\n"); } } void die(struct Connection *conn, const char *message) { if (errno) { perror(message); } else { printf("ERROR: %s\n", message); } Database_close(conn); exit(1); } void Address_print(struct Address *addr) { printf("%d %s %s %s\n", addr->id + 1, addr->name, addr->email, addr->phone); } void Database_load(struct Connection *conn) { assert(conn->db && conn->file); if (!(conn->db && conn->file)) die(conn, "Database load : Invalid Connection info"); if (fread(&conn->db->max_data, sizeof(conn->db->max_data), 1, conn->file) != 1) die(conn, "Database load : Couldn't read MAX_DATA"); if (fread(&conn->db->max_rows, sizeof(conn->db->max_rows), 1, conn->file) != 1) die(conn, "Database load : Couldn't read MAX_ROWS"); conn->db->rows = (struct Address **)malloc(sizeof(struct Address *) * conn->db->max_rows); assert(conn->db->rows); if (!(conn->db->rows)) die(conn, "Database_load : Could not read MAX_ROWS Address structures"); for (int i = 0; i < conn->db->max_rows; i++) { conn->db->rows[i] = (struct Address *)malloc(sizeof(struct Address)); assert(conn->db->rows[i]); if (!(conn->db->rows[i])) die(conn, "Database_load : Could not create Address structure"); if (fread(&conn->db->rows[i]->id, sizeof(conn->db->rows[i]->id), 1, conn->file) != 1) die(conn, "Database_load : Couldn't read ID"); if (fread(&conn->db->rows[i]->set, sizeof(conn->db->rows[i]->set), 1, conn->file) != 1) die(conn, "Database_load : Couldn't read SET"); conn->db->rows[i]->name = (char *)malloc(conn->db->max_data); conn->db->rows[i]->email = (char *)malloc(conn->db->max_data); conn->db->rows[i]->phone = (char *)malloc(conn->db->max_data); // 为 phone 分配内存 if (!conn->db->rows[i]->name || !conn->db->rows[i]->email || !conn->db->rows[i]->phone) die(conn, "Database_load : Could not allocate memory for address data"); if (fread(conn->db->rows[i]->name, conn->db->max_data, 1, conn->file) != 1) die(conn, "Database_load : Couldn't read name"); if (fread(conn->db->rows[i]->email, conn->db->max_data, 1, conn->file) != 1) die(conn, "Database_load : Couldn't read email"); if (fread(conn->db->rows[i]->phone, conn->db->max_data, 1, conn->file) != 1) die(conn, "Database_load : Couldn't read phone"); // 读取 phone 数据 } } struct Connection *Database_open(const char *filename, char mode, int max_data, int max_rows) { struct Connection *conn = malloc(sizeof(struct Connection)); if (!conn) die(NULL, "Memory error"); conn->db = malloc(sizeof(struct Database)); if (!conn->db) die(conn, "Memory error"); if (mode == 'c') { conn->file = fopen(filename, "w"); } else { conn->file = fopen(filename, "r+"); if (conn->file) { Database_load(conn); } } if (!conn->file) die(conn, "Failed to open the file"); else printf("Database open : success\n"); return conn; } void Database_write(struct Connection *conn) { rewind(conn->file); if (fwrite(&conn->db->max_data, sizeof(conn->db->max_data), 1, conn->file) != 1) die(conn, "Database write : Could not write MAX_DATA"); if (fwrite(&conn->db->max_rows, sizeof(conn->db->max_rows), 1, conn->file) != 1) die(conn, "Database write : Could not write MAX_ROWS"); for (int i = 0; i < conn->db->max_rows; i++) { struct Address *cur = conn->db->rows[i]; if (fwrite(&cur->id, sizeof(cur->id), 1, conn->file) != 1) die(conn, "Database write : Could not write ID"); if (fwrite(&cur->set, sizeof(cur->set), 1, conn->file) != 1) die(conn, "Database write : Could not write SET"); if (fwrite(cur->name, conn->db->max_data, 1, conn->file) != 1) die(conn, "Database write : Could not write name"); if (fwrite(cur->email, conn->db->max_data, 1, conn->file) != 1) die(conn, "Database write : Could not write email"); if (fwrite(cur->phone, conn->db->max_data, 1, conn->file) != 1) die(conn, "Database write : Could not write phone"); // 写入 phone 数据 } if (fflush(conn->file) == -1) die(conn, "Database write : Could not flush database"); } void Database_create(struct Connection *conn, int max_data, int max_rows) { conn->db->max_data = max_data; conn->db->max_rows = max_rows; conn->db->rows = (struct Address **)malloc(sizeof(struct Address *) * max_rows); assert(conn->db->rows); if (!(conn->db->rows)) die(conn, "Database_create : Could not allocate memory for rows"); for (int i = 0; i < max_rows; i++) { conn->db->rows[i] = (struct Address *)malloc(sizeof(struct Address)); assert(conn->db->rows[i]); if (!(conn->db->rows[i])) die(conn, "Database_create : Could not create Address structure"); conn->db->rows[i]->id = i; conn->db->rows[i]->set = 0; conn->db->rows[i]->name = (char *)malloc(max_data); conn->db->rows[i]->email = (char *)malloc(max_data); conn->db->rows[i]->phone = (char *)malloc(max_data); // 为 phone 分配内存 if (!conn->db->rows[i]->name || !conn->db->rows[i]->email || !conn->db->rows[i]->phone) die(conn, "Database_create : Could not allocate memory for address data"); memset(conn->db->rows[i]->name, 0, max_data); memset(conn->db->rows[i]->email, 0, max_data); memset(conn->db->rows[i]->phone, 0, max_data); // 初始化 phone 字段 } } void Database_set(struct Connection *conn, int id, const char *name, const char *email, const char *phone) { struct Address *addr = conn->db->rows[id]; if (addr->set) die(conn, "Already set, delete it first"); addr->set = 1; strncpy(addr->name, name, conn->db->max_data); addr->name[conn->db->max_data - 1] = '\0'; strncpy(addr->email, email, conn->db->max_data); addr->email[conn->db->max_data - 1] = '\0'; strncpy(addr->phone, phone, conn->db->max_data); // 设置 phone 字段 addr->phone[conn->db->max_data - 1] = '\0'; } void Database_get(struct Connection *conn, int id) { struct Address *addr = conn->db->rows[id]; if (addr->set) { Address_print(addr); } else { die(conn, "ID is not set"); } } void Database_delete(struct Connection *conn, int id) { struct Address *addr = conn->db->rows[id]; addr->set = 0; } void Database_list(struct Connection *conn) { for (int i = 0; i < conn->db->max_rows; i++) { struct Address *cur = conn->db->rows[i]; if (cur->set) { Address_print(cur); } } } void Database_find(struct Connection *conn, const char *field, const char *value) { if (strcmp(field, "name") == 0) { for (int i = 0; i < conn->db->max_rows; i++) { struct Address *cur = conn->db->rows[i]; if (cur->set && strcmp(cur->name, value) == 0) { Address_print(cur); } } } else if (strcmp(field, "email") == 0) { for (int i = 0; i < conn->db->max_rows; i++) { struct Address *cur = conn->db->rows[i]; if (cur->set && strcmp(cur->email, value) == 0) { Address_print(cur); } } } else if (strcmp(field, "phone") == 0) // 增加 phone 字段的查找功能 { for (int i = 0; i < conn->db->max_rows; i++) { struct Address *cur = conn->db->rows[i]; if (cur->set && strcmp(cur->phone, value) == 0) { Address_print(cur); } } } else { die(conn, "Wrong type"); } } int main(int argc, char *argv[]) { if (argc < 3) die(NULL, "USAGE: ex17 <dbfile> <action> [action params]"); char *filename = argv[1]; char action = argv[2][0]; struct Connection *conn = NULL; int id = 0; int max_data = 0; int max_rows = 0; if (action == 'c') { if (argc != 5) die(NULL, "Need max_data and max_rows to create"); max_data = atoi(argv[3]); max_rows = atoi(argv[4]); conn = Database_open(filename, action, max_data, max_rows); Database_create(conn, max_data, max_rows); Database_write(conn); } else { conn = Database_open(filename, action, 0, 0); if (argc > 3) id = atoi(argv[3]) - 1; if (id > conn->db->max_rows - 1) die(conn, "There's not that many records."); switch (action) { case 'g': if (argc != 4) die(conn, "Need an id to get"); Database_get(conn, id); break; case 's': if (argc != 7) die(conn, "Need id, name, email, phone to set"); Database_set(conn, id, argv[4], argv[5], argv[6]); Database_write(conn); break; case 'd': if (argc != 4) die(conn, "Need id to delete"); Database_delete(conn, id); Database_write(conn); break; case 'l': Database_list(conn); break; case 'f': Database_find(conn, argv[3], argv[4]); break; default: die(conn, "Invalid action, only: c=create, g=get, s=set, d=del, l=list, f=find"); } } Database_close(conn); return 0; }

附加题 6

编写一个脚本来通过以正确顺序运行命令执行自动化测试。

import subprocess import argparse import time def run_commands(program, commands): print("⮞⮞⮞⮞⮞⮞⮞⮞⮞⮞⮞⮞⮞⮞⮞⮞⮞⮞⮞⮞⮞⮞⮞⮞⮞⮞⮞⮞⮞⮞⮞⮞⮞⮞⮞⮞⮞") for command in commands: command_str = " ".join(command) print(f"⮞⮞⮞ {command_str}") result = subprocess.run([program] + command, capture_output=True, text=True) time.sleep(1) if result.stdout: print("⮞⮞⮞ Output:") print(result.stdout) if result.stderr: print("Test Command Error:") print(result.stderr) print("⮞⮞⮞⮞⮞⮞⮞⮞⮞⮞⮞⮞⮞⮞⮞⮞⮞⮞⮞⮞⮞⮞⮞⮞⮞⮞⮞⮞⮞⮞⮞⮞⮞⮞⮞") print("") def main(): parser = argparse.ArgumentParser(description="Run a series of commands to test a specified program.") parser.add_argument('program', help="The program to test, e.g., ./ex17") args = parser.parse_args() base_commands = [ ["db.dat", "l"], ["db.dat", "d", "2"], ["db.dat", "l"], ["db.dat", "g", "2"], ["db.dat", "g", "3"] ] if args.program == './ex17_3': commands = [ ["db.dat", "c", "32", "3"], ["db.dat", "s", "1", "zed", "zed@zedshaw.com", "10086"], ["db.dat", "s", "2", "frank", "frank@zedshaw.com", "10010"], ["db.dat", "s", "3", "joe", "joe@zedshaw.com", "10000"], ["db.dat", "s", "3", "joe", "joe@zedshaw.com", "10000"], ["db.dat", "f", "phone", "10000"], ] + base_commands elif args.program == './ex17_2': commands = [ ["db.dat", "c", "32", "3"], ["db.dat", "s", "1", "zed", "zed@zedshaw.com"], ["db.dat", "s", "2", "frank", "frank@zedshaw.com"], ["db.dat", "s", "3", "joe", "joe@zedshaw.com"], ["db.dat", "s", "3", "joe", "joe@zedshaw.com"], ] + base_commands else: commands = [ ["db.dat", "c"], ["db.dat", "s", "1", "zed", "zed@zedshaw.com"], ["db.dat", "s", "2", "frank", "frank@zedshaw.com"], ["db.dat", "s", "3", "joe", "joe@zedshaw.com"], ["db.dat", "s", "3", "joe", "joe@zedshaw.com"], ] + base_commands run_commands(args.program, commands) if __name__ == "__main__": main()

附加题 7

尝试重构程序,使用单一的全局变量来储存数据库连接。这个新版本和旧版本比起来如何?

加载文件会变得异常麻烦……

附加题 8

搜索“栈数据结构”,并且在你最喜欢的语言中实现它,然后尝试在C中实现。

#include <stdio.h> #include <stdlib.h> #include <stdbool.h> // 定义栈的最大容量 #define MAX 100 // 定义栈结构 typedef struct Stack { int items[MAX]; int top; } Stack; // 初始化栈 void initialize(Stack *s) { s->top = -1; } // 检查栈是否为空 bool isEmpty(Stack *s) { return s->top == -1; } // 检查栈是否已满 bool isFull(Stack *s) { return s->top == MAX - 1; } // 入栈操作 bool push(Stack *s, int value) { if (isFull(s)) { printf("Stack is full. Cannot push %d\n", value); return false; } s->items[++(s->top)] = value; return true; } // 出栈操作 int pop(Stack *s) { if (isEmpty(s)) { printf("Stack is empty. Cannot pop\n"); exit(EXIT_FAILURE); } return s->items[(s->top)--]; } // 获取栈顶元素 int peek(Stack *s) { if (isEmpty(s)) { printf("Stack is empty. Cannot peek\n"); exit(EXIT_FAILURE); } return s->items[s->top]; } // 主函数测试栈操作 int main() { Stack s; initialize(&s); push(&s, 10); push(&s, 20); push(&s, 30); printf("Top element is %d\n", peek(&s)); printf("Stack full: %s\n", isFull(&s) ? "true" : "false"); printf("Stack empty: %s\n", isEmpty(&s) ? "true" : "false"); printf("Popped element is %d\n", pop(&s)); printf("Top element is %d\n", peek(&s)); printf("Popped element is %d\n", pop(&s)); printf("Top element is %d\n", peek(&s)); printf("Popped element is %d\n", pop(&s)); printf("Stack empty: %s\n", isEmpty(&s) ? "true" : "false"); return 0; }
⮞ ./ex17_stack Top element is 30 Stack full: false Stack empty: false Popped element is 30 Top element is 20 Popped element is 20 Top element is 10 Popped element is 10 Stack empty: true