【问题标题】:Weird integer conversion with structs and pointers in C在 C 中使用结构和指针进行奇怪的整数转换
【发布时间】:2021-04-08 04:12:17
【问题描述】:

我正在尝试在 C 中创建一个堆栈,当我“推送”到它时,基于 int*s,在第二次推送之后,我得到了一个奇怪的数字转换,从 -3 到一个奇数。这是我的代码。我感谢任何人的见解。

typedef struct
{
  int current_stack_size;
  int *stack; // base of stack
  int *leading; // extreme top, to place new int
  int *top_of_stack_ptr; // base of top, to retrieve
} MinStack;

MinStack *minStackCreate()
{
  MinStack *stack = malloc(sizeof(MinStack));
  stack->current_stack_size = 0;
  stack->stack = malloc(sizeof(int));
  stack->top_of_stack_ptr = stack->stack;
  stack->leading = stack->stack;
  return stack;
}

void minStackPush(MinStack *obj, int x)
{
  if (obj->current_stack_size == 0)
  {
    *(obj->stack) = x;
    obj->current_stack_size++;
    obj->leading += sizeof(int);
    return;
  }
  else if(obj->current_stack_size > 0){
    obj->current_stack_size++;
    obj->stack = realloc(obj->stack, sizeof(int) * (obj->current_stack_size));
    *(obj->leading) = x;
    obj->leading += sizeof(int);
    obj->top_of_stack_ptr += sizeof(int);
    if (x == -3){  // for debug purposes
      printf("X is -3, so clearly obj->top_ptr = %d\n", obj->top_of_stack_ptr);
    }
  }
void print_state(MinStack *stack, char *op)
{
  printf("Current Stack Size: %d\n", stack->current_stack_size);
  printf("Stack Size:%d Last Operation: %s\n", stack->current_stack_size, op);
  for (int i = 0; i < stack->current_stack_size; i++)
  {
    int value = **((stack->stack) + sizeof(int) * i);
    printf("%d:%d\n", i, value);
  }
  return;
}


int main(void)
    {
      MinStack *stack = minStackCreate();
      minStackPush(stack, -2);
      print_state(stack, "pushed -2");
      minStackPush(stack, 0);
      print_state(stack, "pushed 0");
      minStackPush(stack, -3);
      print_state(stack, "pushed -3");
      minStackPush(stack, -3);
      print_state(stack, "pushed -3");
      minStackPush(stack, -3);
      print_state(stack, "pushed -3");
    ...
    }

这是输出:

Current Stack Size: 1
Stack Size:1 Last Operation: pushed -2
0:-2
Current Stack Size: 2
Stack Size:2 Last Operation: pushed 0
0:-2
1:0
X is -3, so clearly obj->top_ptr = -605719824  // This is unexpected
Current Stack Size: 3
Stack Size:3 Last Operation: pushed -3
0:-2
1:0
2:170932785 // this is unexpected
X is -3, so clearly obj->top_ptr = -500317440
Current Stack Size: 4
Stack Size:4 Last Operation: pushed -3       
0:-2
1:0
2:170932785
3:1884233844

等等

它只发生在第三次推送之后,无论我给它 -3 还是 10。

我很确定它与 realloc 有关,但我不知道具体是什么。

我以为我给缓冲区提供了足够的空间来增长。确实,我将缓冲区前面的前导指针增加了一个 int 的值,但直到我将它增长到足够多时我才写入它。也许我的计算不正确,所以我在两种情况下都给了它一些额外的填充(current_stack_size == 0 或 1)。这并没有解决问题。

我决定一开始就蛮力 malloc 堆栈->堆栈到 sizeof(int) * 50 并且它起作用了。奇怪的是,我的调试输出中的数字仍然是错误的(我在其中写了“这是意外的”),但在堆栈输出本身中它很好。我仍然想知道发生了什么并且能够动态地重新分配它的大小。

然后我想我想通了:我的“leading”和“top_of_stack_ptr”s probalby失去了对stack->stack的内存缓冲区的引用,可以说是在使用realloc时。我相信他们被 realloc “孤立”,与他们的父母分离。我确信这就是发生的事情,而且我得到的输出只是内存中的随机噪声。修复将很容易。而不是stack-&gt;leading += sizeof(int),在obj-&gt;stack = realloc(obj-&gt;stack, sizeof(int) * (obj-&gt;current_stack_size));之后,我做了obj-&gt;leading = obj-&gt;stack + sizeof(int) * obj-&gt;current_stack_size;和obj-&gt;top_of_stack_ptr = obj-&gt;leading - sizeof(int)。它根本没有像我相信的那样解决它。

然后,忘记了最初分配给 sizeof(int)* 50 解决了我的问题,我开始认为我需要 int**s(双指针),因为我需要指向 int 数组的指针。这对我来说很有意义,但给了我一个段错误。我认为问题只是需要在地方和 malloc 中以不同的方式双重取消引用,但这是我对结构所做的更改:

typedef struct
{
  int current_stack_size;
  int **stack;            // base of stack
  int **leading;          // extreme top, to place new int
  int **top_of_stack_ptr; // base of top, to retrieve
} MinStack;

我的段错误就在这里:

void minStackPush(MinStack *obj, int x)
{
  if (obj->current_stack_size == 0)
  {
    *(obj->stack) = x; // THIS creates a seg fault
    printf("Pushing\n");

现在我认为我对双指针的需求是错误的。当您 malloc 时,指向 int 的指针已经是一个数组。双指针可以给你一个二维数组。顺便说一句,我渴望开始学习双指针。这就是 C 语言具有挑战性的原因,也是我喜欢它的原因。尽管我还没有解决这个问题,但有几次我觉得事情对我来说似乎真的很清楚。

那么发生了什么?谢谢!

【问题讨论】:

标签: c pointers realloc


【解决方案1】:

在minStackCreate() 之后,在minStackPush() 中遇到obj-&gt;leading += sizeof(int); 时,obj-&gt;leading 立即指向在minStackCreate() 中分配的内存块MinStack *stack = malloc(sizeof(MinStack)); 当*(obj-&gt;leading) = x; 尝试将整数分配给地址时, 未定义的行为被调用。

内存使用/错误检查

一个简单的内存使用/错误检查详细说明了问题。

$ valgrind ./bin/minstack
==22103== Memcheck, a memory error detector
==22103== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==22103== Using Valgrind-3.13.0 and LibVEX; rerun with -h for copyright info
==22103== Command: ./bin/minstack
==22103==
Current Stack Size: 1
Stack Size:1 Last Operation: pushed -2
0:-2
==22103== Invalid write of size 4
==22103==    at 0x4006A6: minStackPush (minstack.c:36)
==22103==    by 0x4007C9: main (minstack.c:62)
==22103==  Address 0x51f40b0 is 12 bytes after a block of size 4 free'd
==22103==    at 0x4C3023F: realloc (in /usr/lib64/valgrind/vgpreload_memcheck-amd64-linux.so)
==22103==    by 0x40068F: minStackPush (minstack.c:35)
==22103==    by 0x4007C9: main (minstack.c:62)
==22103==  Block was alloc'd at
==22103==    at 0x4C2E01F: malloc (in /usr/lib64/valgrind/vgpreload_memcheck-amd64-linux.so)
==22103==    by 0x4005D0: minStackCreate (minstack.c:18)
==22103==    by 0x400792: main (minstack.c:59)
==22103==
Current Stack Size: 2
Stack Size:2 Last Operation: pushed 0
0:-2
==22103== Invalid read of size 4
==22103==    at 0x400753: print_state (minstack.c:50)
==22103==    by 0x4007DA: main (minstack.c:63)
==22103==  Address 0x51f4540 is 8 bytes after a block of size 8 alloc'd
==22103==    at 0x4C3023F: realloc (in /usr/lib64/valgrind/vgpreload_memcheck-amd64-linux.so)
==22103==    by 0x40068F: minStackPush (minstack.c:35)
==22103==    by 0x4007C9: main (minstack.c:62)
==22103==
1:0
==22103== Invalid write of size 4
==22103==    at 0x4006A6: minStackPush (minstack.c:36)
==22103==    by 0x4007EB: main (minstack.c:64)
==22103==  Address 0x51f40c0 is 16 bytes after a block of size 16 in arena "client"
==22103==
==22103== Invalid read of size 4
==22103==    at 0x4006DE: minStackPush (minstack.c:40)
==22103==    by 0x4007EB: main (minstack.c:64)
==22103==  Address 0x51f40c0 is 16 bytes after a block of size 16 in arena "client"
==22103==
X is -3, so clearly obj->top_ptr = -3
Current Stack Size: 3
Stack Size:3 Last Operation: pushed -3
0:-2
==22103== Invalid read of size 4
==22103==    at 0x400753: print_state (minstack.c:50)
==22103==    by 0x4007FC: main (minstack.c:65)
==22103==  Address 0x51f4590 is 4 bytes after a block of size 12 alloc'd
==22103==    at 0x4C3023F: realloc (in /usr/lib64/valgrind/vgpreload_memcheck-amd64-linux.so)
==22103==    by 0x40068F: minStackPush (minstack.c:35)
==22103==    by 0x4007EB: main (minstack.c:64)
==22103==
1:0
2:0
==22103== Invalid write of size 4
==22103==    at 0x4006A6: minStackPush (minstack.c:36)
==22103==    by 0x40080D: main (minstack.c:66)
==22103==  Address 0x51f40d0 is 32 bytes before a block of size 1,024 in arena "client"
==22103==
==22103==
==22103== Process terminating with default action of signal 11 (SIGSEGV): dumping core
==22103==  Access not within mapped region at address 0x1051F40C4
==22103==    at 0x4006DE: minStackPush (minstack.c:40)
==22103==    by 0x40080D: main (minstack.c:66)
==22103==  If you believe this happened as a result of a stack
==22103==  overflow in your program's main thread (unlikely but
==22103==  possible), you can try to increase the size of the
==22103==  main thread stack using the --main-stacksize= flag.
==22103==  The main thread stack size used in this run was 8388608.
==22103==
==22103== HEAP SUMMARY:
==22103==     in use at exit: 48 bytes in 2 blocks
==22103==   total heap usage: 6 allocs, 4 frees, 1,096 bytes allocated
==22103==
==22103== LEAK SUMMARY:
==22103==    definitely lost: 0 bytes in 0 blocks
==22103==    indirectly lost: 0 bytes in 0 blocks
==22103==      possibly lost: 0 bytes in 0 blocks
==22103==    still reachable: 48 bytes in 2 blocks
==22103==         suppressed: 0 bytes in 0 blocks
==22103== Rerun with --leak-check=full to see details of leaked memory
==22103==
==22103== For counts of detected and suppressed errors, rerun with: -v
==22103== ERROR SUMMARY: 7 errors from 6 contexts (suppressed: 0 from 0)
Segmentation fault (core dumped)

在调用未定义行为时还有两个问题。

    if (x == -3){  // for debug purposes
        printf("X is -3, so clearly obj->top_ptr = %d\n", obj->top_of_stack_ptr);
    }

您试图用%d 打印一个整数指针。如果要打印指针地址,请使用 %p 并将指针转换为 void,例如使用:

        printf("X is -3, so clearly obj->top_ptr = %p\n", (void*)obj->top_of_stack_ptr);

如果你确实想输出整数值,你需要:

        printf("X is -3, so clearly obj->top_ptr = %d\n", *obj->top_of_stack_ptr);

你有类似的问题:

    int value = **((stack->stack) + sizeof(int) * i);

不清楚你是如何编译的,因为这会导致:

    minstack.c:49:17: error: invalid type argument of unary ‘*’ (have ‘int’)
         int value = **((stack->stack) + sizeof(int) * i);

你有太多的一级间接。要修复,您需要:

    int value = *((stack->stack) + sizeof(int) * i);

您将需要解决这些问题。

始终在启用警告的情况下进行编译,并且不要接受代码,直到它在没有警告的情况下进行编译。要启用警告,请将-Wall -Wextra -pedantic 添加到您的gcc/clang 编译字符串(也可以考虑添加-Wshadow 以警告阴影变量)。对于 VS(Windows 上为cl.exe),请使用/W3。所有其他编译器将具有类似的选项。阅读并理解每个警告——然后去修复它。他们将识别任何问题,以及它们发生的确切线路。通过聆听编译器告诉您的内容,您可以学到很多东西。

如果您在进一步解决这些问题时遇到问题,请在下方发表评论,我们很乐意为您提供进一步帮助。


修复索引

清除上面指出的错误后,您的下一个最大问题是:

    int value = *((stack->stack) + sizeof(int) * i);

您的价值在:

    int value = stack->stack[i];

通过包含(address) + (4 * i),您的索引远远超出了尝试打印该值的分配存储空间。

分配逻辑

您的分配逻辑过于复杂。在MinStack *minStackCreate() 中,您正在创建一个MinStack 对象,因此只需为此进行分配。你还不需要为stack-&gt;stack分配,你没有它的价值,所以只需初始化stack-&gt;stack = NULL;,例如

MinStack *minStackCreate()
{
  MinStack *stack = malloc(sizeof(MinStack));
  stack->current_stack_size = 0;
  stack->stack = NULL;
  return stack;
}

现在在int *minStackPush(MinStack *obj, int x) 中,您不需要if ... then ... 条件来处理添加第一个值或后续值。 stack-&gt;stack 初始化为 NULL,所以只需调用 realloc() 为一个额外的整数添加空间并适当地更新指针,例如

int *minStackPush(MinStack *obj, int x)
{
  /* always realloc using temporary pointer to prevent memory leak on failure */
  void *tmp = realloc (obj->stack, (obj->current_stack_size + 1) * sizeof *obj->stack);
  if (!tmp) {
    perror ("realloc-obj->stack");
    return NULL;
  }
  obj->stack = tmp;
  obj->leading = obj->stack + obj->current_stack_size;
  obj->top_of_stack_ptr = obj->leading;
  *obj->leading = x;
  
  obj->current_stack_size++;
  
  return obj->stack;
}

(注意:我已将返回类型更改为int*。您在函数内分配内存,因此您需要一种方法来确定成功/失败。这不是“如果”的问题重新分配将失败,这是重新分配失败的“时间”问题。这与您的回报无关(例如int或int*等...)。事实是您可以确定是否分配成功或失败很重要。选择一个方便合理的返回类型,你可以在调用函数的地方合并返回检查)

您的printf() 造成了无穷无尽的问题。如上所述,您试图访问超出分配范围的内存。你有一个对象stack,并且该对象包含一个指向已分配整数值块的指针(例如stack-&gt;block——用于避免与stack-&gt;stack中的stack混淆的块)。只需将 block 索引为数组,例如stack-&gt;block[i]。当您需要在分配的块中索引一个整数时,只需像使用数组一样进行索引,例如

void print_state(MinStack *stack, char *op)
{
  printf ("Stack Size:%d Last Operation: %s\n",
          stack->current_stack_size, op);
  
  for (int i = 0; i < stack->current_stack_size; i++)
    printf("%d:%d\n", i, stack->stack[i]);
}

把精炼的逻辑放在一起,你会:

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

typedef struct
{
  int current_stack_size;
  int *stack; // base of stack
  int *leading; // extreme top, to place new int
  int *top_of_stack_ptr; // base of top, to retrieve
} MinStack;

MinStack *minStackCreate()
{
  MinStack *stack = malloc(sizeof(MinStack));
  stack->current_stack_size = 0;
  stack->stack = NULL;
  return stack;
}

int *minStackPush(MinStack *obj, int x)
{
  /* always realloc using temporary pointer to prevent memory leak on failure */
  void *tmp = realloc (obj->stack, (obj->current_stack_size + 1) * sizeof *obj->stack);
  if (!tmp) {
    perror ("realloc-obj->stack");
    return NULL;
  }
  obj->stack = tmp;
  obj->leading = obj->stack + obj->current_stack_size;
  obj->top_of_stack_ptr = obj->leading;
  *obj->leading = x;
  
  obj->current_stack_size++;
  
  return obj->stack;
}

void print_state(MinStack *stack, char *op)
{
  printf ("Stack Size:%d Last Operation: %s\n",
          stack->current_stack_size, op);
  
  for (int i = 0; i < stack->current_stack_size; i++)
    printf("%d:%d\n", i, stack->stack[i]);
}


int main(void)
{
    MinStack *stack = minStackCreate();
    for (int i = -5; i < 6; i++) {
        char buf[32];
        minStackPush (stack, i);
        sprintf (buf, "pushed: %d", i);
        print_state (stack, buf);
    }
    
    free (stack->stack);
    free (stack);
}

(注意:您的 top_of_stack_ptr 和 leading 执行相同的功能。您不需要单独的指针来指向应该添加下一个 int 的位置,因为这没什么但是基于current_stack_size 的简单偏移量。所以删除一个或另一个。就个人而言,我会放弃leading,因为top_of_stack_ptr 是你真正想要的)

使用/输出示例

$ ./bin/minstack_fixed
Stack Size:1 Last Operation: pushed: -5
0:-5
Stack Size:2 Last Operation: pushed: -4
0:-5
1:-4
Stack Size:3 Last Operation: pushed: -3
0:-5
1:-4
2:-3
Stack Size:4 Last Operation: pushed: -2
<...snip...>
Stack Size:11 Last Operation: pushed: 5
0:-5
1:-4
2:-3
3:-2
4:-1
5:0
6:1
7:2
8:3
9:4
10:5

内存使用/错误检查

$ valgrind ./bin/minstack_fixed
==29254== Memcheck, a memory error detector
==29254== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==29254== Using Valgrind-3.13.0 and LibVEX; rerun with -h for copyright info
==29254== Command: ./bin/minstack_fixed
==29254==
Stack Size:1 Last Operation: pushed: -5
0:-5
Stack Size:2 Last Operation: pushed: -4
0:-5
1:-4
Stack Size:3 Last Operation: pushed: -3
0:-5
1:-4
2:-3
<...snip...>
Stack Size:11 Last Operation: pushed: 5
0:-5
1:-4
2:-3
3:-2
4:-1
5:0
6:1
7:2
8:3
9:4
10:5
==29254==
==29254== HEAP SUMMARY:
==29254==     in use at exit: 0 bytes in 0 blocks
==29254==   total heap usage: 13 allocs, 13 frees, 1,320 bytes allocated
==29254==
==29254== All heap blocks were freed -- no leaks are possible
==29254==
==29254== For counts of detected and suppressed errors, rerun with: -v
==29254== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)

查看一下,如果您还有其他问题,请告诉我。

【讨论】:

  • 谢谢。 “ int value = *((stack->stack) + sizeof(int) * i);”只是一个错字。输出到整数值 *obj->top_of_stack_ptr) 是一个已修复且控制台正确的疏忽。不过,从 #2 开始的存储桶仍然是错误的。我一遍又一遍地查看所有建议,但仍然得到奇怪的输出。解决这个问题的方法是有一个 current_stack_size + 5) * sizeof(int) 的最小 realloc 值(在 elseif 内)。我不知道为什么,但是任何小于 +5 的东西都会在堆栈中放入错误的数字。
  • @gcr 关键是这个,当你第一次在create()中设置stack-&gt;leading = stack-&gt;stack;时,它指向了stack-&gt;stack的原始地址。当您realloc() 时,该地址没有任何改变。所以stack-&gt;leading 仍然指向原来的stack-&gt;stack,这是realloc() 之后的无效地址。任何更少的值等等..只是未定义行为的副作用。
  • 是的,那个指针被孤立了。我早先将其修复如下(在 realloc 之后):obj-&gt;leading = obj-&gt;stack + (sizeof(int) * obj-&gt;current_stack_size);,我只是没有空间显示。与 top_of_stack_ptr 相同。它仍然产生了意想不到的输出。
  • 还记得指针类型控制指针算法。因此,当您提供stack-&gt;stack + 1 时,新地址将是stack-&gt;stack 指向下一个整数 值之后的4 个字节。 (这也是为什么你不能对 void* 指针进行指针运算——大小信息不完整)
【解决方案2】:

你在记忆中跳跃太多了。

      obj->leading++; // += sizeof(int);
      obj->top_of_stack_ptr++; // += sizeof(int);

和

      int value = *((stack->stack) + /* sizeof(int) * */ i);

应该修正你的指针算术。

其他问题已经从其他答案中指出。

【讨论】:

  • 谢谢。我试过了,但没有 sizeof(int) 对我不起作用。我不知道为什么。一致性?不必那样做就好了。无论如何,宏的好用例。
猜你喜欢
  • 2014-08-13
  • 2014-12-17
  • 2013-02-04
  • 2021-01-29
  • 2014-02-16
  • 1970-01-01
  • 2020-11-28
  • 2012-05-29
  • 1970-01-01
相关资源
最近更新 更多