【问题标题】:Is there potential data corruption when a struct with an array of pointers is malloc'd当具有指针数组的结构被 malloc 时,是否存在潜在的数据损坏
【发布时间】:2016-02-21 22:23:13
【问题描述】:

我想接收三个任意长度的双精度缓冲区。下面是一个简短的例子

struct Data
{
  double *foo[3];
};

int main(void)
{
  double bar1[] = {1.0, 2.0, 3.0};
  double bar2[] = {1.0, 2.0, 3.0, 4.0};
  double bar3[] = {1.0, 2.0, 3.0, 4.0, 5.0};

  struct Data *data = (struct Data*)malloc(sizeof(struct Data));

  data->foo[0] = bar1;
  data->foo[1] = bar2;
  data->foo[2] = bar3;

  printf("%lf %lf %lf\n", data->foo[0][0], data->foo[0][1], data->foo[0][2]);
  printf("%lf %lf %lf %lf\n", data->foo[1][0], data->foo[1][1], 
  data->foo[1][2], data->foo[1][3]);
  printf("%lf %lf %lf %lf %lf\n", data->foo[2][0], data->foo[2][1], 
  data->foo[2][2], data->foo[2][3], data->foo[2][4]);

  return 0;
}

我担心的是,如果我以上述方式 malloc Data 我会冒数据损坏的风险。如果我在不知道大小的情况下在堆上为指向双缓冲区的指针数组(或本质上是任意大小的二维数组)分配内存,那么数据是否受到任何保护?我觉得它有可能覆盖数据。我的这种想法正确吗?这可以编译和打印,但我不确定我是否相信它在更大规模的实现中。

【问题讨论】:

  • 在 C 中使用指针时,总是存在损坏的可能性。甚至以 NUL 结尾的字符串也只是一个约定:当你有一个 char * 时,你可以在内存中的任何地方前进或后退,直到操作系统告诉你你搞砸了,通常是通过一个段错误。
  • 你不应该转换malloc()的结果。我没有这里的链接,但有充分的理由 - 它可以隐藏警告。
  • 好吧,不要覆盖它! ;-)。一个想法是不要将简单的数组作为 foo->Data 成员,而是定义另一个具有 int 作为第一个元素的结构,以保持长度信息。如果这类似于 c++ 向量,那么,好吧..
  • @MadPhysicist 在没有任何指示的情况下,数据损坏的可能性同样大;它只会在以后表现出来,而且可能更微妙。
  • @Talaria 一般来说,您的代码形式没有那么有用。您已经从动态内存中分配了一个struct Data,但是您将成员数组foo[] 的元素设置为指向堆栈上的变量。如果您想从某个函数返回指向以这种方式初始化的struct Data 的指针,那将是不好的,因为data->foo 中的指针现在将指向无效内存。通常,您需要初始化 foo[] 的每个元素以指向已分配的动态内存块,然后初始化这些内存块。

标签: arrays c pointers malloc heap-memory


【解决方案1】:

只要您没有分配错误的值,就没有数据损坏。您必须了解数据的存储位置以及有效时间。例如:

/* !!!! broken code ahead !!!! */
struct Data
{
  double *foo[3];
};

void initData(struct Data* data) {
  double bar1[] = {1.0, 2.0, 3.0};
  double bar2[] = {1.0, 2.0, 3.0, 4.0};
  double bar3[] = {1.0, 2.0, 3.0, 4.0, 5.0};
  data->foo[0] = bar1;
  data->foo[1] = bar2;
  data->foo[2] = bar3;
}

int main(void)
{
  struct Data *data = (struct Data*)malloc(sizeof(struct Data));
  initData(data);

  printf("%lf %lf %lf\n", data->foo[0][0], data->foo[0][1], data->foo[0][2]);
  printf("%lf %lf %lf %lf\n", data->foo[1][0], data->foo[1][1], 
  data->foo[1][2], data->foo[1][3]);
  printf("%lf %lf %lf %lf %lf\n", data->foo[2][0], data->foo[2][1], 
  data->foo[2][2], data->foo[2][3], data->foo[2][4]);

  return 0;
}

这是个坏主意:

  • data 是堆分配的并且“活着”,直到你调用 free
  • bar1..3 是堆栈分配的,并且只存在于 initData() 内部
  • data->foo 指向 bar1..3 并且仅在 initData() 内部有效
  • printf-calls 可能有效(尚未测试),但代码已损坏

正确处理 C 语言是最困难的任务。当您使用 linux 进行开发时,您应该查看 valgrind 以捕获这些类型的错误(我的示例中的错误很明显,但它会变得非常困难)

【讨论】:

  • 有趣。假设数据有一个成员int my_variable。现在,在initData() 中,如果你有int x = 5 后跟data->my_variable = x 就可以了,对吧?它是堆栈上的一个局部变量,但它会受到保护,因为 malloc 会为它分配吗?
  • @Talaria :没问题,是的。但我不确定我是否理解您的解释正确.. data->my_variable 属于结构并分配有malloc。使用data->my_variable = x,您将x 的值5 复制到my_variable,一切正常。 bar1data->foo[0] 的值是{1.0, 2.0, 3.0}地址initData 返回后,此地址无效。
  • 我就是这么想的。感谢所有帮助。
【解决方案2】:

当然,malloc() 本身不会导致数据损坏的风险。如果所讨论的结构是在堆栈上分配的自动变量,那么无论存在什么风险至少都一样大。

您似乎真正要问的是数据结构本身,基本上是一般的指针。是的,如果您有一个指针,则可以通过该指针尝试在指针指向的对象边界之外进行无效的内存访问。 C 对此类尝试不提供保护;它通过声明尝试此类操作的程序的行为未定义来解决此问题。

程序员有责任确保他的程序不会尝试这样的操作。对于指向数组的指针,通常可以通过单独跟踪指向数组的长度来解决该问题,或者使用不能作为普通数据出现的标记值标记数组的末尾。

【讨论】:

    猜你喜欢
    • 2012-06-04
    • 2012-04-18
    • 1970-01-01
    • 1970-01-01
    • 2017-03-10
    • 2012-02-25
    • 1970-01-01
    • 2018-07-02
    • 2021-12-14
    相关资源
    最近更新 更多