1

我正在学习如何在 C 中使用指针和结构。自然地,我试图故意破坏我的代码以进一步了解该语言的工作原理。这是一些按我预期工作的测试代码:

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

struct pair {
    int x;
    int y;
};

typedef struct pair pair;

void p_struct( pair ); //prototype

int main( int argc, char** argv ) {
    pair *s_pair;
    int size, i;

    printf( "Enter the number of pair to make: " );
    scanf( "%d", &size );
    getchar();
    printf( "\n" );

    s_pair = (pair*)malloc( size * sizeof(pair) );

    for( i = 0; i < size; i++ ) {
        s_pair[i].x = i;
        s_pair[i].y = i;
        p_struct( s_pair[i] );
    }

    getchar();

    return (EXIT_SUCCESS);
}

void p_struct( pair s_pair ) {
    printf( "\n%d %d\n", s_pair.x, s_pair.y );
}

如前所述,据我所知,此代码是有效的。

然后我决定修改部分代码,如下所示:

for( i = 0; i < size + 3; i++ ) {
    s_pair[i].x = i;
    s_pair[i].y = i;
    p_struct( s_pair[i] );
}

此修改没有产生我预期的段错误错误。尽管我超出了我在使用 scanf 函数为我的变量大小分配值时明确设置的缓冲区,但所有“对”都被打印出来了。

据我了解指针(如果我错了,请纠正我),当我为类型对s_pair的指针调用 malloc 函数时,堆中的内存管理器会保留一个大小为size*sizeof(pair)的连续内存块。我所做的是,当我将 for 循环修改为条件i < size + 3时,我超出了最后分配的内存地址。

如果我正确理解这一点,我的指针是否超出了其保留的内存限制,并且恰好是清晰的,因为它的右侧和右侧没有被其他数据占用?这是溢出缓冲区时的正常行为吗?

补充一点,当我使用i < size + 15的 for 循环条件进行测试时,我确实收到了段错误。问题是,它仍然打印输出。如中所示,根据我制作的 p_struct 函数,当屏幕上的size = 10时,它将“0 0”对“24 24”打印出来。程序只有在到达底部的 getchar() 之一后才会因段错误而崩溃。我的程序究竟如何将值分配给超出缓冲区的对,将它们打印在屏幕上,然后突然决定在到达 getchar() 时因段错误而崩溃?i < size + 3似乎没有问题(尽管它仍然是错误的)。

作为记录,我还使用常规指针数组测试了这种行为:

int size, i, *ptr;

scanf( "%d", &size );

ptr = (int*)malloc( size * sizeof(int) );

for( i = 0; i < size + 15; i++ )
    ptr[i] = i;

这会产生与上面完全相同的结果。在i < size + 3处,段错误似乎没有任何问题。

最后,我也用一个数组进行了测试:

int i, array[10];

for( i = 0; i < 25; i++ )
    array[i] = i;

对于条件i < 25,我会毫无故障地遇到段错误。当我将其更改为i < 15时,我没有收到段错误。

如果我没记错的话,指针数组和数组之间的唯一区别是分配给数组的内存位于堆栈而不是堆上(对此不确定)。考虑到这一点,并考虑到当array[10]不产生任何段错误时i < 15的事实,为什么i < 25会成为问题?在 for 循环期间,数组不是位于堆栈顶部吗?当它不关心 60 个额外字节时,为什么它会关心 100 个额外字节?为什么该数组缓冲区的上限不是一直到为整个堆栈保留的任意内存块的末尾?

希望所有这些对于决定阅读一个稍微醉酒的人的漫谈的人来说都是有意义的。

4

4 回答 4

2

欢迎来到光荣的 C 世界!

内存分配函数(malloccallocrealloc等)为您提供堆上的内存。当您调用其中一个并且您的程序没有足够的空间时,它会进行系统调用以获取更多空间。但是,它不会以精确的增量执行此操作(它通常会以某些数量的整页增量执行此操作)。当您在数组末尾(或什至在数组开头之前)进行索引时,您仍在程序的合法地址空间范围内。只有当您离开程序拥有的段时,您才会收到Segmentation Violation

我强烈推荐使用Valgrind来检查你的程序,特别是如果你故意尝试通过破坏来了解内存。除其他外,它将在分配的任一侧存储金丝雀值,以帮助您确定何时访问越界并警告您双重释放和内存泄漏。

于 2013-09-02T04:55:05.843 回答
2

如果我正确理解这一点,我的指针是否超出了其保留的内存限制,并且恰好是清晰的,因为它的右侧和右侧没有被其他数据占用?

差不多。除了你不是“清楚”,因为相邻的东西可能其他数据占用,你的代码只是踩在那个内存上并改变了值。您可能永远不会注意到问题,或者您可能会在很久以后才注意到问题。无论哪种方式,它都是未定义的行为。

于 2013-09-02T04:48:59.410 回答
1

正如其他人所说,未定义的行为并不意味着您的程序在所有情况下都会崩溃。

这完全取决于覆盖数据的地方应该有什么。

  • 可能什么都没有,因为 C lib 没有在那里分配程序,
  • 您可能已经覆盖了稍后使用的重要管理信息,然后才会导致崩溃,
  • 或其他任何东西。

为了帮助您了解真正发生的事情,打印地址(例如printf("%p\n", s_pair);或类似的东西)可能会有所帮助,以及将程序编译为可读的汇编助记符(例如gcc -S filename.c -o-

于 2013-09-02T12:44:45.720 回答
1

当您调用 malloc 时,您可能会获得比您需要的更多的内存,因为内存是按公共块大小的倍数分配的。如果块大小是 64 字节并且您只要求 10 字节,那么操作系统会给您 64 字节,因此您仍然可以访问超出您请求范围的内存,这是您的程序正在观察的行为。

于 2013-09-02T10:59:57.373 回答