C:指针混淆

时间:2013-03-27 15:42:55

标签: c pointers binaryfiles

#include<stdio.h>
#include<stdlib.h>


typedef struct{
        char cStdName[50];
        int  nStdNum;
        char cStdClass[4];
        float dStdAvg;
}student;

student* students;

int cmp(const void* a, const void* b);

void main() {
    int num = 0,i=0;
    FILE *f;

    printf("Number of students:");
    scanf("%d", &num);
    students = (student*)malloc(num*sizeof(student));

    for(i=0;i<num;++i){

        student* ptr = students+i*sizeof(student);
        printf("Name:");
        scanf("%s", ptr->cStdName);
        printf("Num");
        scanf("%d", &ptr->nStdNum);
        printf("Class:");
        scanf("%s", ptr->cStdClass);
        printf("Grade:");
        scanf("%f", &ptr->dStdAvg);
    }

    f = fopen("bin.bin","wb");
    fwrite(&num,sizeof(int),1,f);
    fwrite(students,sizeof(student),num,f);
    fclose(f);
    system("pause");
}

这应该在二进制文件中输出学生数和所有结构'数组',并且它适用于1名学生。但是当我添加&gt; = 2个人时,文件看起来像这样: http://i.imgur.com/LgL8fUa.png

如果我只添加一名学生,则仍有一些Windows路径无意义: http://i.imgur.com/s7fm9Uv.png 没关系,读取文件的程序会忽略NULL之后的所有内容(我的意思是,对于第一个char数组)。

我认为问题出现在for()循环和指针玩杂耍的某处,但我不知道在哪里。

4 个答案:

答案 0 :(得分:5)

student* ptr = students + i * sizeof(student);

在C中,指针算法已包含sizeof(student)。你将阅读你的arrray结束。

student* ptr = students + i;

但是,您会注意到访问ptr与访问students[i]相同。

答案 1 :(得分:1)

您的代码存在一些问题:

  • 首先,正如基里连科所说,你应该在你的代码中使用students[i]。在您的情况下,students + i * sizeof(student)超出范围。
  • fwrite与结构数组一起使用绝不是一个好主意。这是因为编译器可能在struct(padding)的成员之间添加一些空格,这意味着当你将一个结构数组传递给fwrite时,将打印填充字节(将包含垃圾)。
  • 同样适用于struct中的char数组成员。所有未使用的字节都将包含垃圾,当您使用fwrite时将打印垃圾。最好使用strlen来确定每个char数组包含的读取字符数。

以下是我将学生的数组写入文件的方法:

void write(students* array, int len, FILE* out)
{
    int i;
    fwrite(len, 1, sizeof(len), out);

    for (i = 0; i < len; i++){
        fwrite(array[i]->cStdName, 1, strlen(array[i]->cStdName), out);
        fwrite(array[i]->nStdNum, 1, sizeof(array[i]->nStdNum), out);
        fwrite(array[i]->cStdClass, 1, strlen(array[i]->cStdClass), out);
        fwrite(array[i]->dStdAvg, 1, sizeof(array[i]->dStdAvg), out);
    }
}

答案 2 :(得分:0)

指针赋值可以在for循环外部进行一次,并且可以在for循环结束时递增指针。试试这个。

确定。这是尝试解释发生了什么,ptr指向学生结构类型列表中的第一个元素,当你在for循环结束时递增ptr时,它指向列表中的下一个学生。


---
students = (student*)malloc(num*sizeof(student));
student* ptr = students;

    for(i=0;i<num;++i){

        printf("Name:");
        ----
        ----
        ptr++;
    }

答案 3 :(得分:0)

基里连科的答案应该可以解决你眼前的问题,但是在这个项目的几乎每一行都有从琐碎到严重的错误。根据您的更大目标,您可能不需要修复所有目标,但无论如何我都要将它们全部写下来,以说明冰山的大小。

void main() {

int main(void)int是绝对必要的。在C(不是C ++)中,为函数参数列表编写()意味着参数是未指定的,而不是没有参数;从技术上讲,你可以在这里使用它,因为这是一个函数定义,但它的风格很糟糕。

函数定义的左大括号总是单独出现在一条线上,即使所有其他开口大括号都被包围。

    int num = 0,i=0;

间距不一致。初始化是不必要的。

    printf("Number of students:");

当您不使用fputs("Number of students", stdout);的格式化功能时,某些样式指南更喜欢printf。但是有些编译器可以为你做转换,这不是什么大问题。

    scanf("%d", &num);

永远不要使用scanffscanfsscanf ,因为:

  1. 数字溢出会触发未定义的行为。允许C运行时使程序崩溃只是因为有人输入了太多数字。
  2. 某些格式说明符(特别是%s,您稍后在此程序中使用!)的安全性与gets不安全的方式完全相同,即它们会愉快地写入提供的缓冲区的末尾并崩溃你的程序(这个特定的程序对我来说看起来并不敏感,但是应该始终编写代码,好像一个人的程序至少有些那样危险)。
  3. 他们很难正确处理格式错误的输入。
  4. 从用户读取单个非负数的正确方法如下:

    unsigned long getul(void)
    {
        char buf[80], *endp;
        unsigned long val;
    
        for (;;) {
            fgets(buf, 80, stdin);
            val = strtoul(buf, &endp, 10);
            if (endp != buf && *endp == '\n')
                return val;
    
            if (buf[strlen(buf)] != '\n')
                while (getchar() != '\n')
                    /* discard */;
            fprintf(stderr, "*** Enter one nonnegative integer, smaller than %lu.\n",
                    ULONG_MAX);
        }
    }
    

    你可以在这里使用固定大小的缓冲区,因为没有人的ULONG_MAX太大而且不适合80个字符。

        students = (student*)malloc(num*sizeof(student));
    

    你应该在这里使用calloc,这样当你把你的结构写到磁盘上时,它们就没有垃圾了。 (或者按照Alexandros的建议编写自定义序列化程序,这也可以避免这个问题。)

        for(i=0;i<num;++i){
    

    首选格式为for (i = 0; i < num; i++) {。除了间距外,请使用i++代替++i,以便i出现在所有三个表达式的相同位置;这使得阅读更容易。

            student* ptr = students+i*sizeof(student);
    

    student* ptr = students + i;正如其他地方所讨论的那样。

            printf("Name:");
            scanf("%s", ptr->cStdName);
    

    见上面的评论。你想要另一个辅助函数,如:

    void getstr(char *buf, size_t len)
    {
        size_t n;
    
        for (;;) {
            fgets(buf, len, stdin);
            n = strlen(buf);
            if (n < len && buf[n] == '\n') {
                memset(buf+n, 0, len-n);
                return;
            }
            while (getchar() != '\n')
                /* discard */;
            fprintf(stderr, "*** Enter no more than %lu characters.",
                    (unsigned long)(len-1));
        }
    }
    

    ...

            scanf("%f", &ptr->dStdAvg);
    

    在这里你需要另一个辅助函数。 getfgetul完全相同,只是它使用strtod,当然错误信息稍有不同。

        f = fopen("bin.bin","wb");
    

    这不是一个非常通用的文件名吗?应该有一种方法供用户指定。

        fwrite(&num,sizeof(int),1,f);
    

    您的文件格式需要magic number

        fwrite(students,sizeof(student),num,f);
    

    您正在以CPU端序的顺序将二进制数据写入磁盘。对于此应用程序而言,这可能不是问题,但请注意,您可能会遇到跨平台兼容性问题。 (就个人而言,对于你正在做的事情,我会使用文本序列化,如JSON,或简单的无守护程序数据库,如sqlite。)请参阅Alexandros的答案,了解此文件格式的更多潜在问题。

    写入磁盘上的文件时很少出现问题,这不是那种输出在某处传输的程序,但我仍然提到fwrite不保证全部写入您提供的数据。从技术上讲,你必须在这样的循环中调用fwrite

    size_t r, n = sizeof(student) * num;
    char *p = (char *)students;
    while (n > 0) {
        r = fwrite(p, 1, n, f);
        if (r == 0) break;  /* write error */
        n -= r;
        p += r;
    }
    

    为了使其正常工作,必须自己进行乘法,并将第二个参数传递给fwrite;否则,短写可能会在“数据元素”的中间结束,而您无法知道这种情况已经发生。

        fclose(f);
    

    关闭文件前检查写入错误。

    if (ferror(f) || fclose(f)) {
        perror("bin.bin");
        return 1; /* unsuccessful exit */
    }
    

    ...

        system("pause");
    

    只需return 0。让你按键退出的程序是批处理不友好的。