fread returns 0 并且无法在 Windows 机器上打开 db 文件。找不到文件?或者是权限?

fread returns 0 and cannot open db file on Windows machine. Can the file not be found? Or is it the permissions?

我正在做一个关于 C (Learn C The Hard Way: Ex17) 的教程,并且在 Database_load 函数中当 fread 加载数据库文件时 "ERROR! Failed to load database." 被抛出。为什么不能读取数据库?该文件位于目录中。找不到文件?或者是否需要更改权限?

#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!\t%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)
{
    // Error shows up here!
    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"); // Problem is here! See @fluter's solution

    if (conn->file == NULL) {
        fprintf(stderr, "open failed, errno %d: %s\n", errno, strerror(errno));
    }

} else {

    conn->file = fopen(filename, "r+"); // And the problem is here!

    if (conn->file == NULL) {
        fprintf(stderr, "open failed, errno %d: %s\n", errno, strerror(errno));
    } else {
        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 the address
        struct Address addr = {.id = i, .set = 0};

        // then assign the prototype to the database
        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("Address already set, delete it first");
    }

    addr->set = 1;

    // WARNING!! Strncpy has a bug - needs to be fixed ...
    char *res = strncpy(addr->name, email, MAX_DATA);

    if(!res) {
           die("Name copy failed.");
    }

    // ... by adding NULL to the end of the array
    addr->name[sizeof(addr->name)-1] = '[=12=]';

    res = strncpy(addr->email, email, MAX_DATA);

    if(!res) {
        die("Email copy failed.");
    }
}

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.exe <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(argc > MAX_ROWS) {
    die("There aren't that many records");
}

switch(action) {
    case 'c':
        Database_create(conn);
        Database_write(conn);
        break;
    case 's':
        if(argc != 4) {
            die("Id, name, and email address are needed.");
        }

        Database_set(conn, id, argv[4], argv[5]);
        Database_write(conn);
        break;
    case 'l':
        Database_list(conn);
        break;
    default:
        die("\tInvalid action.\n\
                \tPlease use:\n\
                \tPlease use:\n\
                \tc = create\n\
                \td = delete\n\
                \tl = list\n\
                \ts = set\n");
    }
Database_close(conn);
return 0;
}

问题是,即使错误显示 "Failed to load database.",文件肯定在文件夹中。

我想知道是不是权限问题(见上面的屏幕截图)。对我来说,我有 rw,但 Admin 只有 r。因为我是 运行 终端机,所以不应该是因为 Admin 没有 w,不是吗?

首先,你打印的是conn->file的地址,而不是conn->file本身,当然conn->file的地址不是NULL。所以请尝试以下操作:

printf("conn->file address: %p\n", conn->file);

如果打开失败会打印NULL,具体错误,打印errno找出错误的根本原因:

if (conn->file == NULL) {
    fprintf(stderr, "open failed, errno %d: %s\n", errno, strerror(errno);
} else {
    // open db
}

对于windows上的失败,是因为Windows直接处理文本和二进制打开的文件,而在Linux上它们是一样的。如果文件以文本模式打开,windows 会将 CRLF 转换为 LF,并将 ctrl-z 控制字符视为 EOF 标记,这正是您在 windows 上看到的错误,因此在 Database_open, 使用以下方式打开文件:

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

这也适用于 Linux,因为文件在 Linux 上是二进制模式。