【问题标题】:Does malloc only allocate memory, nothing else?malloc 是否只分配内存,没有别的?
【发布时间】:2014-03-28 16:49:44
【问题描述】:

代码先行:

struct yo
{
    char *name;
    int several[10];
    int num;
};

int main(void)
{
    struct yo *test_yo = malloc(sizeof(struct yo));
    printf("%p\t%p\t%p\n", (void *)&test_yo->name, (void *)&test_yo->num, (void *)&test_yo->several);
    return 0;
}

我的问题是:如果 malloc 只分配一些内存空间并返回一个指向它的指针,那么 test_yo 如何找到它的名称、num 和几个的地址 - 因为 test_yo 只是指向一堆垃圾的指针? malloc 是否真的在这里做了一些额外的工作,比如对齐结构成员和记录成员的内存地址?

【问题讨论】:

  • 编译器执行类型检查后,struct 只不过是偏移量的集合。 malloc 完全不知道数据类型、结构或类似的东西。你不想sizeof(struct yo) 字节?给你,一个 18 字节的新块。

标签: c malloc


【解决方案1】:

编译器可以解决这个问题。即sizeof 计算出所需的空间,编译器计算出如何排列该内存中的各种项目

【讨论】:

    【解决方案2】:

    malloc 函数本身不会对齐结构的成员,这是由编译器在程序运行之前很久就完成的。例如,在具有 8 字节指针和 4 字节整数的系统上,很可能:

    • name 将位于结构的开头;
    • several 将在偏移量 8 处;和
    • num 将位于偏移量 48(以允许 several 具有十个四字节元素)。

    malloc 所做的只是给你一些至少足够大的内存来放入那个结构(或者如果它找不到足够的内存,给你 NULL)。

    字段结构中的定位是由访问所述结构的代码完成的。

    【讨论】:

      【解决方案3】:

      这与malloc无关。发生的事情非常简单 - 通过指针 X 引用结构成员只需将偏移量添加到存储在指针 X 中的内存地址(其值)。因此,您只需将偏移量应用于 malloc 返回的指针。

      请记住,编译后的二进制文件中不存在结构 - 它只是高级编程语言的编译时“概念”。您甚至可以使用offsetof 宏获取结构中每个成员的偏移量。

      因此,从哪里获得指针以及它的值是什么并不重要,您始终可以通过将其偏移量(编译器始终知道)添加到结构的“字段”地址来计算存储在指针中的值。

      这里有一个小例子来证明这一点:

      #include <stddef.h>
      #include <stdio.h>
      
      struct my_struct {
              int x;
              int y;
      };
      
      int main()
      {
              struct my_struct *s;
      
              /* Print relative offsets of the structure member fields: */
              printf("Offset of 'x': %lu\n", offsetof(struct my_struct, x));
              printf("Offset of 'y': %lu\n", offsetof(struct my_struct, y));
      
              /*
               * Assign some random memory address to a pointer and print addresses
               * of the structure and its members. You can see that they are related,
               * pointer to 'x' will always be the same as pointer to structure +
               * `offsetof(struct my_struct, x)` and pointer to 'y' as pointer to
               * structure + `offsetof(struct my_struct, y)`
               */
              s = (void *)0xDEADBEEF;
              printf("Pointer to structure: %p\n", s);
              printf("Pointer to x: %p (x - base = %ld)\n",
                     &s->x, (ptrdiff_t)&s->x - (ptrdiff_t)s);
              printf("Pointer to y: %p (y - base = %ld)\n",
                     &s->y, (ptrdiff_t)&s->y - (ptrdiff_t)s);
      
              return 0;
      }
      

      不同平台上的结果可能不同,这是我在 Intel x86_64 上的结果:

      $ clang -Weverything -o test ./test.c && ./test
      Offset of 'x': 0
      Offset of 'y': 4
      Pointer to structure: 0xdeadbeef
      Pointer to x: 0xdeadbeef (x - base = 0)
      Pointer to y: 0xdeadbef3 (y - base = 4)
      

      请注意,您始终可以在运行时计算和字段偏移量。与offsetof 的主要区别在于它可以用于计算compile-time 中的偏移量。

      另外,尝试自己计算字段偏移时要小心。虽然编译器无法重新排列字段,但它可以轻松执行padding and alignment,如果需要,可以在字段成员之间有效地添加隐式“空”空间。

      希望对您有所帮助。祝你好运!

      【讨论】:

      • 所以真正发生的事情是test_yo首先获取地址,然后编译器认为test_yo是一个指向struct yo的指针,所以编译器在test_yo指向的内存中对齐不同的成员,对吗?
      • @Xufeng:当然。 .-&gt; 运算符只是方便的 C 语法。如果你知道他们的偏移量,你可以引用没有这些运算符的成员(这是人们在汇编中编程必须做的事情)。
      • @Xufeng:我会稍微纠正一下措辞-当您通过指针引用字段成员时,编译器不会对齐任何内容。它在第一次看到声明时对齐结构 - 此时它决定每个字段的偏移量。当您通过指针引用成员时,它只是应用它之前计算的偏移量。
      • 如果是这样,当编译器第一次看到结构时,它会对齐结构并定义其所有成员吗?这就是为什么我可以引用它的成员,即使我只创建一个指向整个结构的指针?
      • @Xufeng:是的,你几乎明白了。
      【解决方案4】:

      malloc 只分配内存。 C 编译器在编译时知道结构 'yo' 包含三个字段,包括一个名为 'name' 的字段,该字段位于结构偏移量 0 处,等等。

      因此,编译器采用从 malloc 返回的指针,添加 known 结构字段偏移量,最终得到您描述的直接指向成员的地址。

      【讨论】:

        【解决方案5】:

        malloc 只是分配特定数量的内存。它返回一个通用指针(即void*)。

        编译器看到结构,知道它的布局。

        如果你有不同的类型,你会得到不同的值(注意下面的代码使用完全相同的指向堆分配内存的指针)。

        struct foo { int i, j, k };
        struct bar { char i, j, k; };
        void *ptr = malloc(100);
        struct foo * fooptr = (struct foo *)ptr;
        struct bar * barptr = (struct bar *)ptr;
        printf("foo: %p, %p, %p\n", &fooptr->i, &fooptr->j, &fooptr->k);
        printf("bar: %p, %p, %p\n", &barptr->i, &barptr->j, &barptr->k);
        

        【讨论】:

          【解决方案6】:

          C 中的结构是一个非常……嗯……结构化的内存块。假设主机系统具有 32 位指针和 32 位整数,那么name(a pointer) 将始终位于结构的偏移量 0 处; several 的第一个元素将始终位于结构的偏移量 4 处(偏移量 0 + {指针大小}); andnum 将始终位于结构的偏移量 44 处(偏移量 4 + 10 * {sizeof an int})。偏移量可能因平台和编译器而异,具体取决于数据类型大小、内存对齐等,但对于给定的平台编译器组合,偏移量总是相同的。对于我上面给出的示例,struct yo 类型的每个项目将正好是 48 个字节长,例如,num总是在结构中的偏移量 44 处。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2010-10-12
            • 2012-07-28
            • 2017-07-06
            • 2013-10-13
            • 1970-01-01
            相关资源
            最近更新 更多