5

我想了解:

  • 为什么有时char[1]C 中的 a 被用作char*(为什么这样做?)和
  • 内部是如何工作的(发生了什么)

给出以下示例程序:

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

struct test_struct {
    char *a;
    char b[1];
} __attribute__((packed)); ;

int main() {

    char *testp;
    struct test_struct test_s;

    testp = NULL;
    memset(&test_s, 0, sizeof(struct test_struct));

    printf("sizeof(test_struct) is: %lx\n", sizeof(struct test_struct));

    printf("testp at: %p\n", &testp);
    printf("testp is: %p\n", testp);

    printf("test_s.a at: %p\n", &test_s.a);
    printf("test_s.a is: %p\n", test_s.a);

    printf("test_s.b at: %p\n", &test_s.b);
    printf("test_s.b is: %p\n", test_s.b);

    printf("sizeof(test_s.b): %lx \n", sizeof(test_s.b));

    printf("real sizeof(test_s.b): %lx \n", ((void *)(&test_s.b) - (void *)(&test_s.a)) );

    return 0;
}

我得到以下输出(OS X,64 位):

sizeof(test_struct) is: 9
testp at: 0x7fff62211a98
testp is: 0x0
test_s.a at: 0x7fff62211a88
test_s.a is: 0x0
test_s.b at: 0x7fff62211a90
test_s.b is: 0x7fff62211a90
sizeof(test_s.b): 1 
real sizeof(test_s.b): 8 

查看内存地址,可以看到即使结构是 9 字节大,也分配了 16 字节,这似乎是由char b[1]. 但是我不确定这些额外的字节是否是由于优化/内存对齐的原因而分配的,或者这是否与 C 对 char 数组的内部处理有关。

一个真实世界的例子可以在<fts.h>

`man 3 fts` 将结构成员 `fts_name` 显示为:

            char *fts_name;                 /* file name */

而 /usr/include/fts.h 将成员定义为:

            char fts_name[1];               /* file name */

最后,fts_name真的可以用作指向 C 字符串的指针。例如,使用printf("%s", ent->fts_name)作品打印到标准输出。

所以如果 achar[1]真的是一个字节大,它就不能在我的 64 位机器上用作内存指针。另一方面,将其视为完全成熟char *也不起作用,正如上面的test_s.b is输出所示,它应该显示一个 NULL 指针......

4

2 回答 2

2

这是一个描述技巧的答案。char[1]基本上,这个想法是在构造结构时分配更多内存malloc(),以便在没有额外分配的情况下为您的字符串提供一些存储空间。您有时甚至可以看到char something[0]用于相同目的,这更不直观。

另一方面,将其视为完整的 char * 也不起作用,正如上面输出的 test_s.b 所示,它应该显示一个 NULL 指针......

如果某个东西是一个数组,那么它的名称和&name只是在 C 中给出指向数组开头的指针。无论它是结构中的成员还是独立变量,这都有效。

printf("real sizeof(test_s.b): %lx \n", ((void *)(&test_s.b) - (void *)(&test_s.a)) );

这一行给出了分配给 的空间大小a,而不是b在这个结构中。在后面放一些东西b并用它来减去。使用该packed属性(这意味着您不允许编译器弄乱对齐等),您应该得到 1。

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

struct test_struct {
    char *a;
    char b[1];
    char c;
} __attribute__((packed));

int main() {
  struct test_struct s;
  printf("%lx\n", ((void*)&s.c) - ((void*)&s.b));
  return 0;
}

我明白了1

于 2012-10-12T17:06:18.480 回答
2

当 C 不是您的母语时,这是可以理解的。首先要弄清楚几件事。

在 C 中,所有var[n]方法都是“获取 表示的地址,向该地址var添加n*sizeof(var's type)字节,然后返回结果地址。另外值得注意的是,C 语言不会阻止您经过数组声明的大小。

您经常会在旨在覆盖更大、更重要的可变长度内存分配的结构尾部找到您正在查看的格式。在这种结构中,习惯(通常是强制性的)让先前的结构成员之一指示尾缓冲区空间的实际有效字节。

例子:

typedef struct X
{
   unsigned int count;
   char data[1];
} X;

这与声明指针成员明显不同,指针成员只不过是一个保存地址的变量。

typedef struct Y
{
    unsigned int count;
    char *dataptr;
} Y;

在 Y 中,dataptr 拥有一个地址(并且也有一个)。在 X 中,data 地址。

那么为什么要这样做呢?看看这个。以下内存转储假定小端、1 字节结构打包,以及 4 字节的整数和指针本机大小:

0x00000000  0x10 0x00 0x00 0x00 0x01 0x02 0x03 0x04 
0x00000008  0x05 0x06 0x07 0x08 0x09 0x0A 0x0B 0x0C
0x00000010  0x0D 0x0E 0x0F 0x10;

现在在上面覆盖一个 struct X ,你有

count : 16
data[] : { 0x01, 0x02, 0x03, ... 0x010 };

在此之上叠加 astruct Y将产生明显不同的结果。

count : 16
dataptr : 0x01020304

请记住,在 C 中,您可以轻松地(通常是悲惨地)离开数组声明大小的末尾。这种覆盖技术只不过是对这种能力的一种利用。鉴于上述情况,struct X您可以执行以下操作:

struct X * pX = funcThatReturnsTheMemoryAddressAbove();
for (unsigned int i=0; i<pX->count; i++)
{
   do something with pX->data[i];
}

显然,您需要警惕如何分配管理内存来执行此类操作。

不确定这是否有助于解决问题,但希望有所帮助。

于 2012-10-12T17:18:56.030 回答