【发布时间】:2016-07-17 19:27:02
【问题描述】:
我尝试使用 C 编写一个简单的数据库。但是,我尝试调试我的分段错误并发现通过 malloc 获得的内存指针似乎在变化(名称和电子邮件指针似乎在 Database_load 程序执行之前和之后指向不同的内存位置)。我有两个问题:
为什么执行 Database_load 前后内存指针(姓名和电子邮件)指向不同的位置?
为什么程序会产生段错误?
这里是与问题相关的代码
#include <stdio.h>
#include <assert.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
int MAX_DATA;
int MAX_ROWS;
struct Address {
int id;
int set;
//int MAX_DATA;
char *name;
char *email;
};
struct Database {
//int MAX_ROWS;
struct Address *rows;
};
struct Connection{
FILE *file;
struct Database *db;
};
void die(const char *message){
if(errno){
//perror(message);
printf("ERROR: %s\n", 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 i;
int rc = fread(conn->db, sizeof(struct Database), 1, conn->file);
if(rc != 1) die("Failed to load database.");
for (i = 0; i < MAX_ROWS; i++) {
printf("test Database_load loop read rows %p\n", &conn->db->rows[i]);
printf("test Database_load loop read rows name %p\n", &conn->db->rows[i].name);
printf("test Database_load loop read rows email %p\n", &conn->db->rows[i].email);
printf("test Database_load loop read rows name %s\n", conn->db->rows[i].name);
printf("test Database_load loop start %d\n", i);
rc = fread(&conn->db->rows[i], sizeof(struct Address), 1, conn->file);
printf("test Database_load loop read rows %d\n", i);
rc = fread(&conn->db->rows[i].name, sizeof(MAX_DATA), 1, conn->file);
printf("test Database_load loop read name %d\n", i);
rc = fread(&conn->db->rows[i].email, sizeof(MAX_DATA), 1, conn->file);
printf("test Database_load loop read email %d\n", i);
if(rc != 1) die("Failed to load database.");
printf("test Database_load loop\n");
}
}
struct Connection *Database_open(const char *filename, char mode){
int i = 0;
struct Connection *conn = malloc(sizeof(struct Connection));
if(!conn) die("Memory error no connection");;
conn->db = malloc(sizeof(struct Database));
if(!conn->db) die("Memory error no database");
conn->db->rows = malloc(sizeof(struct Address) * MAX_ROWS);
if (conn->db->rows == NULL) die("No memory for rows");
for(i = 0; i < MAX_ROWS; i++){
// make a prototype to initialize it
//struct Address addr = {.id = i, .set = 0};
conn->db->rows[i].id = i;
conn->db->rows[i].set = 0;
conn->db->rows[i].name = malloc(sizeof(char) * MAX_DATA);
if (conn->db->rows[i].name == NULL) die("No memory for name");
conn->db->rows[i].email = malloc(sizeof(char) * MAX_DATA);
if (conn->db->rows[i].email == NULL) die("No memory for email");
// then just assign it
if (i == 0) {
printf("test set name = %p\n", &conn->db->rows[i].name);
printf("test set email = %p\n", &conn->db->rows[i].email);
}
}
if(mode == 'c'){
conn->file = fopen(filename, "w");
}
else{
conn->file = fopen(filename, "r+"); //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){
int i = 0;
rewind(conn->file);
int rc = fwrite(conn->db, sizeof(struct Database), 1, conn->file);
if(rc != 1) die("Failed to write database.");
for (i = 0; i < MAX_ROWS; i++) {
rc = fwrite(&conn->db->rows[i], sizeof(struct Address), 1, conn->file);
if(rc != 1) die("Failed to write database.");
rc = fwrite(&conn->db->rows[i].name, sizeof(MAX_DATA), 1, conn->file);
if(rc != 1) die("Failed to write database.");
rc = fwrite(&conn->db->rows[i].email, sizeof(MAX_DATA), 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 MAX_DATA, int MAX_ROWS){
int i = 0;
conn->db->rows = malloc(sizeof(struct Address) * MAX_ROWS);
if (conn->db->rows == NULL) die("No memory for rows");
for(i = 0; i < MAX_ROWS; i++){
// make a prototype to initialize it
struct Address addr = {.id = i, .set = 0};
addr.name = malloc(sizeof(char) * MAX_DATA);
if (addr.name == NULL) die("No memory for name");
addr.email = malloc(sizeof(char) * MAX_DATA);
if (addr.email == NULL) die("No memory for email");
// 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: intentional bug, no relevant this question
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> <MAX_ROWS> <MAX_DATA> [action params]");
char *filename = argv[1];
char action = argv[2][0];
MAX_DATA = atoi(argv[3]);
MAX_ROWS = atoi(argv[4]);
int id = 0;
if(argc > 5) id = atoi(argv[5]);
struct Connection *conn = Database_open(filename, action);
// legacy code, does not apply for create case
// if(argc > 3) id = atoi(argv[3]);
// if(id >= MAX_ROWS) die("There's not that many records.");
switch(action){
case 'c':
if(argc != 5) die("Need MAX_DATA and MAX_ROWS");
Database_create(conn, MAX_DATA, MAX_ROWS);
Database_write(conn);
break;
case 'g':
if(argc != 6) die("Need an id to get");
Database_get(conn, id);
break;
case 's':
if(argc != 8) die("Need id, name, email to set");
Database_set(conn, id, argv[6], argv[7]);
Database_write(conn);
break;
case 'd':
if(argc != 6) 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;
}
这是我执行程序后的 printf 输出
$./ex17_arbitrary db_arbitrary.dat c 512 100
$./ex17_arbitrary db_arbitrary.dat s 512 100 1 zed zed@zedshaw.com
test set name = 0x15ad058
test set email = 0x15ad060
test Database_load loop read rows (nil)
test Database_load loop read rows name 0x8
test Database_load loop read rows email 0x10
我确实注意到的一件事是,这两行在使用相同命令的多次执行中永远不会改变
test Database_load loop read rows name 0x8
test Database_load loop read rows email 0x10
更新: 我还有一些额外的设计问题。看起来当前数据结构的设计是有问题的。我将在这里详细说明设计要求:
除了我创建的功能之外,我不需要任何额外的功能。数据库的大小(MAX_DATA 和 MAX_ROWS 必须是可变的)。现在我每次调用程序时都在输入 MAX_DATA 和 MAX_ROWS。这可以改进吗?我在想当我需要使用 Database_create 方法时可能只给出 MAX_DATA 和 MAX_ROWS 。这个程序来自 (c.learncodethehardway.org/book/ex17.html) 中的一个有趣的练习,原始程序有一个固定大小的数据库。目标是使其变成可变大小。
【问题讨论】:
-
在变量上设置读取观察点并检查访问它的位置。
-
发布
MAX_ROWS定义。像i < MAX_ROWS和int MAX_ROWS;这样的代码是可疑的。 -
@chux,我发布了主要内容,并删除了结构中的 MAX_ROWS 和 MAX_DATA 。它们实际上是全局变量(是的,全局变量名称和结构中的变量名称相同),我从不在结构中使用 MAX_ROWS 和 MAX_DATA 。我已经重新编译了程序。并给出了新的输出以及我使用的命令行参数。希望这会有所帮助。
-
发布一个最小的可编译示例。例如,我们不知道您的函数是否有原型。您是否包含了必要的标题。您是否在启用警告的情况下调用编译器。很多问题,几乎没有信息。未定义的行为可能会导致您描述的效果,但是因为没有足够的信息。另外,我认为这是我的观点,但这似乎是首选方式,一行
if语句会使代码更难阅读。 -
@iharob,已粘贴完整代码。编译时没有特殊的编译标志。
标签: c pointers segmentation-fault