当我尝试索引零以外的任何值时,指向结构索引超出范围(?)的指针 [英] Pointer to pointer of structs indexing out of bounds(?) when I try to index anything other than zero

查看:80
本文介绍了当我尝试索引零以外的任何值时,指向结构索引超出范围(?)的指针的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我试图通过使用初始化的新结构填充默认值来编辑结构数组(作为指针).这样做似乎会引起一些非常奇怪的问题.我正在学习如何将结构体与指针配合使用,因此可以提供任何帮助.

主要功能的摘要(播放器仅保存startLoc而不对其进行更改)

Location** near;
    startLoc = initLocation("Base", 5);
    player = initPlayer(startLoc);
    near = &(startLoc->near);
    *near = initLocation("Zero", 0);
    *(near + 1) = initLocation("Two", 0);
    printf("Start near: %i\n", startLoc->nearCount);

整个位置.c

#include <stdlib.h>

typedef struct Location {
    char isValid;
    char* name;
    struct Location* near;
    int nearCount;
} Location;

Location* initLocation(char* name, int nearCount) {
    Location* l = malloc(sizeof(Location));
    l->name = name;
    l->near = calloc(sizeof(Location) * nearCount, 1);
    l->nearCount = nearCount;
    l->isValid = 1;
    return l;
}

解决方案

让我们从有关 pointer pointer-to-pointer 的基本讨论开始.指针只是一个变量,将其他地址作为其值.当您声明指向某物的指针时,就像您对struct中的namenear成员所做的那样,您声明了一个变量,该变量将将地址保留在内存中该类型对象存储在内存中的位置(例如,指针将指向指向该对象的存储位置)

当您声明要键入的 pointer-to-pointer (例如Location **near)时,您将拥有一个 pointer ,其中保存了另一个 pointer的地址作为其值.这可以通过两种方式来使用. (1)它可以允许您将指针的地址作为 parameter 传递,以便该函数能够对该地址处的原始指针进行操作,或者(2)它可以允许该单个指针指向内存中的指针集合,例如

 pointer
    |      pointers      allocated struct
   near --> +----+     +-------------------+
            | p1 | --> | struct Location 1 |
            +----+     +-------------------+
            | p2 | --> | struct Location 2 |
            +----+     +-------------------+
            | p3 | --> | struct Location 3 |
            +----+     +-------------------+
            | .. |     |        ...        |

    (a pointer-to-pointer to type struct Location)

在第二种情况下,为什么选择 pointer-to-pointer 作为您的类型,而不是仅仅分配该类型的集合?好问题.主要原因有两个,一个是您要分配的内容大小可能不同.例如:

 char**
    |      pointers      allocated strings
  words --> +----+     +-----+
            | p1 | --> | cat |
            +----+     +-----+--------------------------------------+
            | p2 | --> | Four score and seven years ago our fathers |
            +----+     +-------------+------------------------------+
            | p3 | --> | programming |
            +----+     +-------------------+
            | .. |     |        ...        |

或(2),您希望分配一个偶数个对象的分配集合(例如,将上面的char**更改为int**),这些对象可以使用2D数组索引(例如,array[2][7])进行寻址

分配 pointers objects 的集合会增加复杂性,因为您负责维护两个分配的集合,指针和对象本身.在释放分配的指针块之前,必须先跟踪并重新分配指针集合(和对象-如果需要),然后再free()对象集合,并进行跟踪.

如果只需要一些相同类型的对象(例如N - struct Location),则可以大大简化此操作.这样就为这些对象本身提供了一次分配,一次重新分配和一次免费(当然,每个对象也可以包含分配的对象).在您的情况下,对于near,它类似于:

 pointer
    |
   near --> +-------------------+
            | struct Location 1 |
            +-------------------+
            | struct Location 2 |
            +-------------------+
            | struct Location 3 |
            +-------------------+
            |        ...        |

       (a pointer to type struct Location)

在您的情况下,您需要处理嵌套分配的struct Location块.从这个意义上讲,只要需要,您只需要N - struct Location即可,它们都具有相同的大小,并且对2D数组索引没有迫切的需求.从这个角度来看,看看您要做什么(最好的猜测),简单地分配struct Location的块而不是处理指向单独分配的struct Location的单独的指针块似乎更有意义.

实施简短示例

虽然initLocation()设置单个struct Location没什么问题,但您可能会发现,编写一次addLocation()函数以在每次添加新struct Location到您的收藏集时都更有意义.叫做.如果您在调用方中初始化指向集合NULL的指针,则可以简单地使用realloc()来处理初始分配和后续的重新分配.

在下面的示例中,我们只为列表中的每个名称创建一个新的struct Location并分配3- near对象.您可以自由使用每个对象中的addLocation()near struct Location,就像您使用初始集合一样,但是该实现留给您,因为它只是在嵌套的基础上做同样的事情.

以类似于您尝试的方式将addLocation()函数放在一起,您可以执行以下操作:

Location *addLocation (Location *l, size_t *nmemb, char *name, int nearCount)
{
    /* realloc using temporary pointer adding 1 Location */
    void *tmp = realloc (l, (*nmemb + 1) * sizeof *l);  /* validate EVERY allocation */
    if (!tmp) {     /* on failure */
        perror ("error: realloc-l");
        return NULL;    /* original data good, that's why you realloc to a tmp */
    }

    /* on successful allocation */
    l = tmp;                                /* assign reallocated block to l */
    l[*nmemb].isValid = 1;                  /* assign remaining values and */
    l[*nmemb].name = name;                  /* allocate for near */
    l[*nmemb].near = calloc(nearCount, sizeof(Location));
    if (!l[*nmemb].near) {
        perror ("calloc-l[*nmemb].near");
        return NULL;
    }
    l[*nmemb].nearCount = nearCount;        /* set nearCount */
    (*nmemb)++;                             /* increment nmemb */

    return l;  /* return pointer to allocated block of Location */
}

然后您可以循环填充每个类似于以下内容的内容:

    for (size_t i = 0; i < nmemb;)  /* loop adding 1st nmemb names */
        if (!(l = addLocation (l, &i, names[i], nearCount)))
            break;

(注意: iaddLocation中进行了更新,因此在循环定义中不需要i++)

一个完整的例子可以写成如下.我添加了打印功能和删除所有已分配内存的功能.在下面的addLocation调用中,您将看到使用names[i%nnames]而不是names[i]并使用计数器 modulo ,列表中的名称总数仅确保列表中的名称为提供,无论i多大.

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

typedef struct Location {
    char isValid;
    char *name;
    struct Location *near;
    int nearCount;
} Location;

Location *addLocation (Location *l, size_t *nmemb, char *name, int nearCount)
{
    /* realloc using temporary pointer adding 1 Location */
    void *tmp = realloc (l, (*nmemb + 1) * sizeof *l);  /* validate EVERY allocation */
    if (!tmp) {     /* on failure */
        perror ("error: realloc-l");
        return NULL;    /* original data good, that's why you realloc to a tmp */
    }

    /* on successful allocation */
    l = tmp;                                /* assign reallocated block to l */
    l[*nmemb].isValid = 1;                  /* assign remaining values and */
    l[*nmemb].name = name;                  /* allocate for near */
    l[*nmemb].near = calloc(nearCount, sizeof(Location));
    if (!l[*nmemb].near) {
        perror ("calloc-l[*nmemb].near");
        return NULL;
    }
    l[*nmemb].nearCount = nearCount;        /* set nearCount */
    (*nmemb)++;                             /* increment nmemb */

    return l;  /* return pointer to allocated block of Location */
}

void prn_locations (Location *l, size_t nmemb)
{
    for (size_t i = 0; i < nmemb; i++)
        if (l[i].isValid)
            printf ("%-12s    nearCount: %d\n", l[i].name, l[i].nearCount);
}

void del_all (Location *l, size_t nmemb)
{
    for (size_t i = 0; i < nmemb; i++)
        free (l[i].near);   /* free each structs allocated near member */

    free (l);   /* free all struct */
}

int main (int argc, char **argv) {

    char *endptr,   /* use with strtoul conversion, names below */
        *names[] = { "Mary", "Sarah", "Tom", "Jerry", "Clay", "Bruce" };
    size_t  nmemb = argc > 1 ? strtoul (argv[1], &endptr, 0) : 4,
            nnames = sizeof names / sizeof *names;
    int nearCount = 3;      /* set nearCourt */
    Location *l = NULL;     /* pointer to allocated object */

    if (errno || (nmemb == 0 && endptr == argv[1])) {   /* validate converstion */
        fputs ("error: nmemb conversion failed.\n", stderr);
        return 1;
    }

    for (size_t i = 0; i < nmemb;)  /* loop adding 1st nmemb names */
        if (!(l = addLocation (l, &i, names[i%nnames], nearCount)))
            break;

    prn_locations (l, nmemb);
    del_all (l, nmemb);
}

使用/输出示例

$ ./bin/locationalloc
Mary            nearCount: 3
Sarah           nearCount: 3
Tom             nearCount: 3
Jerry           nearCount: 3

或者,例如,如果您想分配其中的10个,则:

$ ./bin/locationalloc 10
Mary            nearCount: 3
Sarah           nearCount: 3
Tom             nearCount: 3
Jerry           nearCount: 3
Clay            nearCount: 3
Bruce           nearCount: 3
Mary            nearCount: 3
Sarah           nearCount: 3
Tom             nearCount: 3
Jerry           nearCount: 3

内存使用/错误检查

在您编写的任何可动态分配内存的代码中,您对分配的任何内存块都有2个职责:(1)始终保留指向起始地址的指针因此,(2)当不再需要它时,可以释放.

当务之急是使用一个内存错误检查程序来确保您不尝试访问内存或不在分配的块的边界之外/之外写,尝试读取或基于未初始化的值进行条件跳转,最后,以确认您释放了已分配的所有内存.

对于Linux valgrind是正常选择.每个平台都有类似的内存检查器.它们都很容易使用,只需通过它运行程序即可.

$ valgrind ./bin/locationalloc
==13644== Memcheck, a memory error detector
==13644== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==13644== Using Valgrind-3.13.0 and LibVEX; rerun with -h for copyright info
==13644== Command: ./bin/locationalloc
==13644==
Mary            nearCount: 3
Sarah           nearCount: 3
Tom             nearCount: 3
Jerry           nearCount: 3
==13644==
==13644== HEAP SUMMARY:
==13644==     in use at exit: 0 bytes in 0 blocks
==13644==   total heap usage: 9 allocs, 9 frees, 1,728 bytes allocated
==13644==
==13644== All heap blocks were freed -- no leaks are possible
==13644==
==13644== For counts of detected and suppressed errors, rerun with: -v
==13644== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)

始终确认已释放已分配的所有内存,并且没有内存错误.

让我知道这是否符合您的意图以及您是否还有其他疑问.

I'm trying to edit an array (as a pointer) of structs by filling in the default values with new structs that I initialize. Doing so seems to cause some really bizarre problems. I'm learning how to use structs with pointers, so any help is appreciated.

Snippet from the main function (player just saves the startLoc without changing it)

Location** near;
    startLoc = initLocation("Base", 5);
    player = initPlayer(startLoc);
    near = &(startLoc->near);
    *near = initLocation("Zero", 0);
    *(near + 1) = initLocation("Two", 0);
    printf("Start near: %i\n", startLoc->nearCount);

Entire location.c

#include <stdlib.h>

typedef struct Location {
    char isValid;
    char* name;
    struct Location* near;
    int nearCount;
} Location;

Location* initLocation(char* name, int nearCount) {
    Location* l = malloc(sizeof(Location));
    l->name = name;
    l->near = calloc(sizeof(Location) * nearCount, 1);
    l->nearCount = nearCount;
    l->isValid = 1;
    return l;
}

解决方案

Let's start with a basic discussion about a pointer and a pointer-to-pointer. A pointer is simply a variable that holds the address of something else as its value. When you declare a pointer to something, as you have done with your name or near members within your struct you declare a variable that will hold the address in memory where that type object is stored in memory (e.g. the pointer will point to where that object is stored)

When you declare a pointer-to-pointer to type (e.g. Location **near) you have a pointer that holds the address of another pointer as its value. That can be useful in two ways. (1) it can allow you to pass the address of a pointer as a parameter so that the function is able to operate on the original pointer at that address, or (2) it can allow that single pointer to point to a collection of pointers in memory, e.g.

 pointer
    |      pointers      allocated struct
   near --> +----+     +-------------------+
            | p1 | --> | struct Location 1 |
            +----+     +-------------------+
            | p2 | --> | struct Location 2 |
            +----+     +-------------------+
            | p3 | --> | struct Location 3 |
            +----+     +-------------------+
            | .. |     |        ...        |

    (a pointer-to-pointer to type struct Location)

In the second case, why choose a pointer-to-pointer as your type instead of just allocating for a collection of that type? Good question. There are two primary reasons, one would be if what you were allocating for can vary in size. For example:

 char**
    |      pointers      allocated strings
  words --> +----+     +-----+
            | p1 | --> | cat |
            +----+     +-----+--------------------------------------+
            | p2 | --> | Four score and seven years ago our fathers |
            +----+     +-------------+------------------------------+
            | p3 | --> | programming |
            +----+     +-------------------+
            | .. |     |        ...        |

or (2) where you want an allocated collection of an even number of objects (such as changing char** above to int**) that can be addressed using 2D-array indexing (e.g. array[2][7])

Allocating for a collection of pointers and objects adds complexity because you are responsible for maintaining two allocated collections, the pointers, and the objects themselves. You must track and reallocate for both your collection of pointers (and the objects -- if needed) and then free() your collection of objects before freeing your allocated block of pointers.

This can be greatly simplified, if you just need some number of the same type object, such as N - struct Location. That gives you a single allocation, single reallocation and single free for those objects themselves (of course each object can in turn contain allocated objects as well). In your case for near it would be similar to:

 pointer
    |
   near --> +-------------------+
            | struct Location 1 |
            +-------------------+
            | struct Location 2 |
            +-------------------+
            | struct Location 3 |
            +-------------------+
            |        ...        |

       (a pointer to type struct Location)

In your case you are dealing with needing nested allocated blocks of struct Location. In that sense, where required, you simply need N - struct Location which will all be of the same size and there isn't a compelling need for 2D array indexing. From that standpoint, looking at what you are trying to do (to the best possible guess), simply allocating for blocks of struct Location rather than handling separate blocks of pointers pointing to individually allocated struct Location would seem to make much more sense.

Implementing A Short-Example

While there is nothing wrong with an initLocation() to set up a single struct Location, you may find it makes more sense to simply write an addLocation() function to add a new struct Location to your collection each time it is called. If you initialize your pointer to the collection NULL back in the caller, you can simply use realloc() to handle your initial allocation and subsequent reallocations.

In the following example, we just create a new struct Location for each name in a list and allocate for 3-near objects. You are free to use addLocation() with the near struct Location in each object just as you have with your initial collection, but that implementation is left to you as it is simply doing the same thing on a nested basis.

Putting an addLocation() function together in a manner that looks like what you are attempting, you could do:

Location *addLocation (Location *l, size_t *nmemb, char *name, int nearCount)
{
    /* realloc using temporary pointer adding 1 Location */
    void *tmp = realloc (l, (*nmemb + 1) * sizeof *l);  /* validate EVERY allocation */
    if (!tmp) {     /* on failure */
        perror ("error: realloc-l");
        return NULL;    /* original data good, that's why you realloc to a tmp */
    }

    /* on successful allocation */
    l = tmp;                                /* assign reallocated block to l */
    l[*nmemb].isValid = 1;                  /* assign remaining values and */
    l[*nmemb].name = name;                  /* allocate for near */
    l[*nmemb].near = calloc(nearCount, sizeof(Location));
    if (!l[*nmemb].near) {
        perror ("calloc-l[*nmemb].near");
        return NULL;
    }
    l[*nmemb].nearCount = nearCount;        /* set nearCount */
    (*nmemb)++;                             /* increment nmemb */

    return l;  /* return pointer to allocated block of Location */
}

You could then loop filling each with something similar to:

    for (size_t i = 0; i < nmemb;)  /* loop adding 1st nmemb names */
        if (!(l = addLocation (l, &i, names[i], nearCount)))
            break;

(note: i is being updated in addLocation so there is no need for i++ in your loop definition)

A complete example could be written as follows. I have added a print function and a function to delete all allocated memory as well. In the call to addLocation below, you will see names[i%nnames] used instead of names[i] and using the counter modulo the total number of names in my list just ensures that a name from the list is provided, no matter how big i gets.

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

typedef struct Location {
    char isValid;
    char *name;
    struct Location *near;
    int nearCount;
} Location;

Location *addLocation (Location *l, size_t *nmemb, char *name, int nearCount)
{
    /* realloc using temporary pointer adding 1 Location */
    void *tmp = realloc (l, (*nmemb + 1) * sizeof *l);  /* validate EVERY allocation */
    if (!tmp) {     /* on failure */
        perror ("error: realloc-l");
        return NULL;    /* original data good, that's why you realloc to a tmp */
    }

    /* on successful allocation */
    l = tmp;                                /* assign reallocated block to l */
    l[*nmemb].isValid = 1;                  /* assign remaining values and */
    l[*nmemb].name = name;                  /* allocate for near */
    l[*nmemb].near = calloc(nearCount, sizeof(Location));
    if (!l[*nmemb].near) {
        perror ("calloc-l[*nmemb].near");
        return NULL;
    }
    l[*nmemb].nearCount = nearCount;        /* set nearCount */
    (*nmemb)++;                             /* increment nmemb */

    return l;  /* return pointer to allocated block of Location */
}

void prn_locations (Location *l, size_t nmemb)
{
    for (size_t i = 0; i < nmemb; i++)
        if (l[i].isValid)
            printf ("%-12s    nearCount: %d\n", l[i].name, l[i].nearCount);
}

void del_all (Location *l, size_t nmemb)
{
    for (size_t i = 0; i < nmemb; i++)
        free (l[i].near);   /* free each structs allocated near member */

    free (l);   /* free all struct */
}

int main (int argc, char **argv) {

    char *endptr,   /* use with strtoul conversion, names below */
        *names[] = { "Mary", "Sarah", "Tom", "Jerry", "Clay", "Bruce" };
    size_t  nmemb = argc > 1 ? strtoul (argv[1], &endptr, 0) : 4,
            nnames = sizeof names / sizeof *names;
    int nearCount = 3;      /* set nearCourt */
    Location *l = NULL;     /* pointer to allocated object */

    if (errno || (nmemb == 0 && endptr == argv[1])) {   /* validate converstion */
        fputs ("error: nmemb conversion failed.\n", stderr);
        return 1;
    }

    for (size_t i = 0; i < nmemb;)  /* loop adding 1st nmemb names */
        if (!(l = addLocation (l, &i, names[i%nnames], nearCount)))
            break;

    prn_locations (l, nmemb);
    del_all (l, nmemb);
}

Example Use/Output

$ ./bin/locationalloc
Mary            nearCount: 3
Sarah           nearCount: 3
Tom             nearCount: 3
Jerry           nearCount: 3

Or, for example if you wanted to allocate for 10 of them, then:

$ ./bin/locationalloc 10
Mary            nearCount: 3
Sarah           nearCount: 3
Tom             nearCount: 3
Jerry           nearCount: 3
Clay            nearCount: 3
Bruce           nearCount: 3
Mary            nearCount: 3
Sarah           nearCount: 3
Tom             nearCount: 3
Jerry           nearCount: 3

Memory Use/Error Check

In any code you write that dynamically allocates memory, you have 2 responsibilities regarding any block of memory allocated: (1) always preserve a pointer to the starting address for the block of memory so, (2) it can be freed when it is no longer needed.

It is imperative that you use a memory error checking program to ensure you do not attempt to access memory or write beyond/outside the bounds of your allocated block, attempt to read or base a conditional jump on an uninitialized value, and finally, to confirm that you free all the memory you have allocated.

For Linux valgrind is the normal choice. There are similar memory checkers for every platform. They are all simple to use, just run your program through it.

$ valgrind ./bin/locationalloc
==13644== Memcheck, a memory error detector
==13644== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==13644== Using Valgrind-3.13.0 and LibVEX; rerun with -h for copyright info
==13644== Command: ./bin/locationalloc
==13644==
Mary            nearCount: 3
Sarah           nearCount: 3
Tom             nearCount: 3
Jerry           nearCount: 3
==13644==
==13644== HEAP SUMMARY:
==13644==     in use at exit: 0 bytes in 0 blocks
==13644==   total heap usage: 9 allocs, 9 frees, 1,728 bytes allocated
==13644==
==13644== All heap blocks were freed -- no leaks are possible
==13644==
==13644== For counts of detected and suppressed errors, rerun with: -v
==13644== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)

Always confirm that you have freed all memory you have allocated and that there are no memory errors.

Let me know if this comports with your intent and whether you have any additional questions.

这篇关于当我尝试索引零以外的任何值时,指向结构索引超出范围(?)的指针的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆