访问指向结构的指针会导致内存读取冲突

时间:2020-08-23 01:04:15

标签: c pointers memory struct

由于大量的空闲时间,最近我在C语言中进行了大量编程。由于C(我认为)是一种很难掌握和学习的语言,所以我会犯错,并且内存泄漏左右,偶尔会陷入一个似乎无法解决的问题,就像我现在遇到的那样。

我的代码旨在成为一个简单的Bignum实现:有一点用于对数字进行签名,一个整数列表以及一个用于实际存在多少个整数的计数器。因为两百亿还不够!

无论如何,这是我的代码:

#include <stdlib.h>

const unsigned int INTSZ = (int)sizeof(int), CHRSZ = (int)sizeof(char);

typedef struct {
    unsigned int negative : 1;
    unsigned int count;
    unsigned int* list;
} VLENNUM_I;

typedef struct {
    unsigned int negative : 1;
    unsigned int count;
    unsigned char* list;
} VLENNUM_C;

int ceiling(double x) {
    return (((int)x) == x) ? (int)x : (int)x + 1;
}

VLENNUM_I* vlnc_to_vlni(VLENNUM_C* toconv) {
    VLENNUM_I result = { 0 };
    VLENNUM_C drtc = *toconv;

    result.negative = drtc.negative;
    result.count = ceiling(drtc.count / INTSZ);
    result.list = calloc(result.count, INTSZ);

    unsigned int i = 0, temp = 0, toprl = 0;
    for (; i < drtc.count; ++i) {
        temp |= drtc.list[i] << (3 - i % 4) * 8; // Right here

        if (i > 0 && !((i + 1) % 4)) {
            result.list[toprl] = temp;
            temp = 0;
            ++toprl;
        }
    }

    if (!(i % 4)) result.list[toprl + 1] = temp;

    return &result;
}

VLENNUM_C* vlni_to_vlnc(VLENNUM_I* toconv) {
    VLENNUM_C result = { 0 };
    VLENNUM_I drtc = *toconv;

    result.negative = drtc.negative;
    result.count = drtc.count * INTSZ;
    result.list = calloc(result.count, CHRSZ);

    unsigned int i = 0, c = 0, masks[4] = { 255, 65280, 16711680, 4278190080 };

    for (; i < drtc.count; ++i)
        for (int j = 0; j < 4; ++j)
            result.list[(i * 4) + (3 - j)] = (char)((drtc.list[i] & masks[j]) >> (j * 8));

    return &result;
}

int main(void) {
    VLENNUM_I x = { 0 };

    x.count = 1;
    x.negative = 0;
    x.list = malloc(1 * INTSZ);

    x.list[0] = 172639;

    VLENNUM_C* y = vlni_to_vlnc(&x);

    VLENNUM_I* z = vlnc_to_vlni(y);

    return 1;
}

VLENNUM_IVLENNUM_C是“可变长度数字”,其中包含int s或char s的列表。 vlnc_to_vlniVLENNUM_C转换为VLENNUM_I,反之亦然vlni_to_vlnc。输入和输出位于指针中,以防传递较大的值,因此实际上返回的是整数,而不是表示结构的大量数据。在Visual Studio 2020中运行代码会导致内存读取错误,我在注释中指出了该错误;使用VS的调试器单步执行代码会产生一些有用的信息,尽管对我而言这些信息毫无意义:toconv充满了某种垃圾数据。例如,结构中包含的count变量将替换为随机数,而不是应有的随机数。有人可以帮我找出这意味着什么以及如何解决吗?

2 个答案:

答案 0 :(得分:2)

vlnc_to_vlnivlni_to_vlnc都有致命缺陷:

VLENNUM_I* vlnc_to_vlni(VLENNUM_C* toconv) {
    VLENNUM_I result = { 0 };
    // ...
    return &result;
}

VLENNUM_C* vlni_to_vlnc(VLENNUM_I* toconv) {
    VLENNUM_C result = { 0 };
    // ...
    return &result;
}

您将返回局部变量的地址,这是结束内存错误的快速方法。操作系统使用调用堆栈跟踪程序的执行情况,该程序在程序启动时如下所示:

[  main: x, y, z ]

这部分调用栈(称为 stack frame )具有当前函数的地址(main)和该函数的局部变量。致电vlni_to_vlnc时:

[ main: x, y, z ][ vlni_to_vlnc: result, drtc, i, c, masks ]

该函数获取自己的堆栈框架,并为其自身的本地空间留出空间。返回&result时,您将返回以下地址:

[ main: x, y, z ][ vlni_to_vlnc: result, drtc, i, c, masks ]
                                 ^^^^^^

但是函数结束时,堆栈框架消失了,这给您留下了这样的指针:

[ main: x, y, z ]                [????]
                                 ^^^^^^

调用vlnc_to_vlni时,其堆栈框架将移至vlni_to_vlnc所在的位置:

[ main: x, y, z ][ vlnc_to_vlni: result, drtc, i, c, masks ]
                                 ^^^^^^ whoops!

简而言之,您的VLENNUM_I *指向新分配的堆栈帧,然后将其写入-因此,您期望的数据将被更改。

在这种情况下,解决方案是执行以下操作之一:

  • 按值返回结构
  • 使用malloc动态分配它,然后在以后释放它
  • 将结果指针作为参数(例如vlnc_to_vlni(VLENNUM_C *toconv, VLENNUM_I *out))并将结果存储在其中

答案 1 :(得分:0)

崩溃是因为代码通过返回其地址在两个函数中形成了指向本地堆栈变量的指针。这些结构要么需要在堆上分配,要么需要从其他范围引用。

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

const unsigned int INTSZ = (int)sizeof(int), CHRSZ = (int)sizeof(char);

typedef struct
{
    unsigned int  negative : 1;
    unsigned int  count;
    unsigned int* list;
} VLENNUM_I;

typedef struct
{
    unsigned int   negative : 1;
    unsigned int   count;
    unsigned char* list;
} VLENNUM_C;

int ceiling(double x)
{
    return (((int)x) == x) ? (int)x : (int)x + 1;
}

void vlnc_to_vlni(VLENNUM_C* toconv, VLENNUM_I* result)
{
    VLENNUM_C drtc   = *toconv;
    assert(result != NULL);

    result->negative = drtc.negative;
    result->count    = ceiling(drtc.count / INTSZ);
    result->list     = (unsigned int*)calloc(result->count, INTSZ);

    unsigned int i = 0, temp = 0, toprl = 0;
    for (; i < drtc.count; ++i)
    {
        temp |= drtc.list[i] << (3 - i % 4) * 8;  // Right here

        if (i > 0 && !((i + 1) % 4))
        {
            result->list[toprl] = temp;
            temp               = 0;
            ++toprl;
        }
    }

    if (!(i % 4))
        result->list[toprl + 1] = temp;
}

void vlni_to_vlnc(VLENNUM_I* toconv, VLENNUM_C *result)
{
    VLENNUM_I drtc   = *toconv;
    assert(result != NULL);

    result->negative = drtc.negative;
    result->count    = drtc.count * INTSZ;
    result->list     = (unsigned char*)calloc(result->count, CHRSZ);

    unsigned int i = 0, c = 0, masks[4] = {255, 65280, 16711680, 4278190080};

    for (; i < drtc.count && result && result->list; ++i)
    {
        for (int j = 0; j < 4; ++j)
        {
            int k = (i * 4) + (3 - j);
            if (k < drtc.count)
                result->list[k] = (char)((drtc.list[i] & masks[j]) >> (j * 8));
        }
    }

}

int main(void)
{
    VLENNUM_I x = {0};

    x.count    = 1;
    x.negative = 0;
    x.list     = (unsigned int*)malloc(1 * INTSZ);

    x.list[0] = 172639;

    VLENNUM_C y = {0};
    vlni_to_vlnc(&x, &y);

    VLENNUM_I z = {0};
    vlnc_to_vlni(&y, &z);

    return 1;
}