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

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

问题描述

我试图通过用我初始化的新结构填充默认值来编辑结构数组(作为指针).这样做似乎会导致一些非常奇怪的问题.我正在学习如何使用带有指针的结构,因此感谢您提供任何帮助.

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.

来自主函数的片段(玩家只保存 startLoc 而不更改它)

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
", 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;
}

推荐答案

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

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)

当你声明一个 pointer-to-pointer 来输入时(例如 Location **near),你有一个保存地址的 pointer另一个指针作为它的值.这在两个方面很有用.(1) 它可以允许您将指针的地址作为 参数 传递,以便函数能够对该地址处的原始指针进行操作,或者 (2) 它可以允许单个指针指向内存中的一组指针,例如

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 |
            +----+     +-------------------+
            | .. |     |        ...        |

或 (2) 您想要分配的偶数对象集合(例如将上面的 char** 更改为 int**)可以寻址使用二维数组索引(例如 array[2][7])

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])

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

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.

这可以大大简化,如果您只需要一些相同类型的对象,例如N - struct Location.这为这些对象本身提供了一次分配、一次重新分配和一次空闲(当然,每个对象也可以依次包含已分配的对象).对于 near,它类似于:

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)

在您的情况下,您正在处理需要 嵌套 分配的 struct Location 块.从这个意义上说,在需要时,您只需要 N - struct Location,它们都将具有相同的大小,并且没有对 2D 数组索引的强烈需求.从这个角度来看,看看你想要做什么(最好的猜测),简单地分配 struct Location 块,而不是处理指向单独分配的 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.

实现一个简短的例子

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

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.

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

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.

addLocation() 函数以一种看起来像您正在尝试的方式组合在一起,您可以这样做:

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;

(注意: i 正在 addLocation 中更新,因此循环中不需要 i++定义)

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

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

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
", 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.
", 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 个分配,则:

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

内存使用/错误检查

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

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.

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

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天全站免登陆