首页 > 解决方案 > 指向同一个地址问题的解释

问题描述

我无法理解指向同一地址的多个指针以及如何解决它。具体来说,我将编写一个适合这种情况的简单代码。

目标:将 csv 文件中的值存储到结构指针的动态数组中。

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

typedef struct data data_t;
struct data
{
    char *name;
    int id;
    double balance;
};

int main(int argc, char **argv)
{
    FILE *fp = NULL;
    data_t **array = (data_t **) malloc(10 * sizeof(data_t *));
    if (argc < 2)
    {
        printf("No file input\n");
        exit(1);
    }
    if (fp = fopen(argv[1], "r") == NULL)
    {
        printf("File not found\n");
        exit(1);
    }
    char buffer[128];
    char delim[2] = ',';
    int index = 0;
    while(fgets(buffer, 128+1, fp))
    {
        data_t customer;
        customer.name = strtok(buffer, delim);
        customer.id = atoi(strtok(NULL, delim));
        customer.balance = atof(strtok(NULL, delim));
        array[index] = (data_t *) malloc(sizeof(data_t));
        array[index] = &customer;
        index += 1;
    }
    fclose(fp);
    fp = NULL;
}

不管是否存在一些愚蠢的错误。

当试图打印这个数组的第一个元素的内容时,我总是得到最后一个元素的内容。我认为这是“指向同一地址”的问题,但不知道如何解决。而且我无法正确打印出名称。

标签: c

解决方案


继续我的评论,你有很多小问题。

首先,始终在启用 FULL 警告的情况下进行编译,并且在没有警告的情况下编译之前不要接受代码。要启用警告,请添加到您的编译字符串(也可以考虑添加以警告阴影变量)。对于VS(在 Windows 上),使用. 所有其他编译器将具有类似的选项。阅读并理解每个警告——然后去修复它。-Wall -Wextra -pedanticgcc/clang-Wshadowcl.exe/W3

这将首先披露缺少的括号:

    if ((fp = fopen(argv[1], "r")) == NULL)     /* parenthesis req'd */

它会显示您的下一个错误与您的声明有关delimstrtok()将字符串作为分隔符列表(它是常量,所以正确):

    const char *delim = ",\n";                  /* don't forget \n */

注意:包括'\n',否则您将留下'\n'最后一个令牌)

没有+1所需的大小fgets()。如果您使用sizeof要填充的数组,则不会出错,例如

    while (fgets (buffer, sizeof buffer, fp))

现在我们开始解决strtok()每个名称的实际问题和存储问题。char *name;在每个结构中是一个未初始化的指针,它不指向任何地方。虽然您可以为其分配一个指针,但您不想简单地为所有.name成员分配相同的指针地址,否则它们最终都会指向同一个地方。

相反,您需要为每个名称分配存储空间并将当前名称复制到分配的内存块中并保留该名称。例如

        data_t customer;
        char *tmp;          /* temporary pointer to use w/strtok() */
        size_t len;
        
        tmp = strtok (buffer, delim);           /* token for name */
        if (!tmp) {                             /* validate token */
            fputs ("error: strtok() name.\n", stderr);
            return 1;
        }
        
        len = strlen (tmp);                     /* get length */
        customer.name = malloc (len + 1);       /* allocate for string */
        if (!customer.name) {                   /* validate EVERY allocation */
            perror ("malloc-customer.name");
            return 1;
        }
        
        memcpy (customer.name, tmp, len + 1);   /* copy tmp to .name */

注意:strcpy()当您已经知道要复制的字符数时,无需再次扫描字符串结尾,只需使用memcpy()它)

同样,您有“分配相同的指针”问题array[index]。您想复制 to 的data_t customer;内容array[index]。您为新结构分配存储空间(干得好),然后用&customer创建内存泄漏的地址覆盖分配块的地址。

分配结构,而不是地址。成员将被复制到您想要的分配结构中,例如

        array[index] = malloc (sizeof *array[index]);   /* allocate struct */
        *array[index] = customer;                       /* assign struct */
        index += 1;

注意:分配malloc()使用取消引用的指针(例如*array[index])来设置类型大小。如果你总是使用取消引用的指针而不是类型——你永远不会弄错类型大小。此外,在 C 中,有不需要强制转换的返回malloc,没有必要。请参阅:我是否转换了 malloc 的结果?

现在事情应该会好一些。别忘了free (array[i].name)在你面前free (array[i])

此外,如上所述,如果您有 POSIX,则strdup()可以在单个操作中进行分配和复制。但请注意,strdup()它不是标准 C,因此使用它可能会导致可移植性问题。但是,如果这样做,您可以简单地:

        /* if you have POSIX strdup(), you can simply allocate and copy
         * in one operation
         */
        strdup (customer.name, tmp);

但...

        /* since strdup() allocates, you must validate */
        if (!customer.name) {
            perror ("strdup-customer.name");
            return 1;
        }

它节省了一两行代码,但代价是标准 C 的可移植性。(由你决定)

最后,避免它们提供零错误检查,并且很乐意接受atoi()静默返回,而没有任何指示发生错误。使用and代替,或者至少在您至少获得转换成功或失败指示的地方使用。atof()atoi ("my cow")0strtol()strtod()sscanf()

完整示例

挥之不去的其他问题是您对MagicNumbers的使用。不要使用它们,而是:

#define NELEM  10       /* if you need a constant, #define one (or more) */
#define MAXB  128

您还需要保护已分配的指针块,以免尝试访问未分配的指针。你在你的阅读循环中这样做,例如

    /* protect the allocation bounds, you only have NELEM pointers */
    while (index < NELEM && fgets (buffer, sizeof buffer, fp))

包含所有验证的快速修订看起来像:

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

#define NELEM  10       /* if you need a constant, #define one (or more) */
#define MAXB  128

typedef struct data data_t;
struct data
{
    char *name;
    int id;
    double balance;
};

int main(int argc, char **argv)
{
    FILE *fp = NULL;
    data_t **array = malloc (NELEM * sizeof *array);
    if (!array) {                               /* validate EVERY allocation */
        perror ("malloc-array");
        return 1;
    }
    
    if (argc < 2) {
        printf("No file input\n");
        exit(1);
    }
    if ((fp = fopen(argv[1], "r")) == NULL) {   /* parenthesis req'd */
        printf("File not found\n");
        exit(1);
    }
    
    char buffer[MAXB];
    const char *delim = ",\n";                  /* don't forget \n */
    int index = 0;
    
    /* protect the allocation bounds, you only have NELEM pointers */
    while (index < NELEM && fgets (buffer, sizeof buffer, fp))
    {
        data_t customer;
        char *tmp;          /* temporary pointer to use w/strtok() */
        size_t len;
        
        tmp = strtok (buffer, delim);           /* token for name */
        if (!tmp) {                             /* validate token */
            fputs ("error: strtok() name.\n", stderr);
            return 1;
        }
        len = strlen (tmp);                     /* get length */
        customer.name = malloc (len + 1);       /* allocate for string */
        if (!customer.name) {                   /* validate EVERY allocation */
            perror ("malloc-customer.name");
            return 1;
        }
        memcpy (customer.name, tmp, len + 1);   /* copy tmp to .name */
        
        if (!(tmp = strtok(NULL, delim))) {     /* token & validations */
            fputs ("error: strtok() - id.\n", stderr);
            return 1;
        }
        /* MINIMAL conversion validation with sscanf() */
        if (sscanf (tmp, "%d", &customer.id) != 1) {
            fputs ("error: invalid integer value - id.\n", stderr);
            return 1;
        }
        
        if (!(tmp = strtok(NULL, delim))) {     /* token & validations */
            fputs ("error: strtok() - balance.\n", stderr);
            return 1;
        }
        if (sscanf (tmp, "%lf", &customer.balance) != 1) {  /* dito */
            fputs ("error: invalid integer value - balance.\n", stderr);
            return 1;
        }
        
        array[index] = malloc (sizeof *array[index]);   /* allocate struct */
        if (!array[index]) {                            /* validate!! */
            perror ("malloc-array[index]");
            return 1;
        }
        
        *array[index] = customer;                       /* assign struct */
        index += 1;
    }
    fclose(fp);
    fp = NULL;      /* not needed, but doesn't hurt anything */
    
    /* use array[], then don't forget to free memory */
    for (int i = 0; i < index; i++) {
        printf ("\nname    : %s\n"
                "id      : %d\n"
                "balance : %.2f\n", 
                array[i]->name, array[i]->id, array[i]->balance);
        free (array[i]->name);      /* free allocated block for name */
        free (array[i]);            /* free allocated array index */
    }
    free (array);                   /* don't forget to free pointers */
}

注意:你不使用assert(),所以不需要包含标题)

由于strtok()可以NULL在失败时返回,因此您应该在数字转换中使用指针之前将所有指针分配给tmp然后验证和if (!tmp) { fputs ("error: strtok() - name.\n", stderr); return 1; }相同。您应该至少对每个数字转换使用简单的验证,如上所示。理想情况下,您会使用并提供更多关于错误的信息。IDbalancesscanf()strtol()strtod()

祝你编码好运!


推荐阅读