【问题标题】:C char array as pointersC char 数组作为指针
【发布时间】:2012-10-12 16:40:47
【问题描述】:

我想了解:

  • 为什么有时 C 中的 char[1] 被用作 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 数组的内部处理有关。

一个真实世界的例子可以在&lt;fts.h&gt;看到:

`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-&gt;fts_name) 打印到标准输出就可以了。

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

【问题讨论】:

  • 主要思想:我会让你失望,但指针不是数组。
  • real sizeof(test_s.b): 8 错误。因为sizeof(char*)8sizeof(char)1。这就是为什么你的结构是 9 字节。
  • 阅读comp.lang.c FAQ的第6节;这是对 C 中数组和指针之间(通常令人困惑的)关系的出色解释。

标签: c arrays pointers


【解决方案1】:

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

另一方面,将其视为完整的 char * 是行不通的 或者,从上面的输出 test_s.b 可以看出,应该 然后显示一个 NULL 指针...

如果某个东西是一个数组,它的名称和&amp;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

【讨论】:

  • 非常感谢您的帮助以及您提供的answer 链接!
【解决方案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;

现在在上面覆盖一个结构 X,你就有了

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

在上面覆盖struct 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];
}

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

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

【讨论】:

    猜你喜欢
    • 2021-01-26
    • 1970-01-01
    • 2012-03-19
    • 1970-01-01
    • 2018-09-26
    • 2023-03-17
    • 1970-01-01
    • 1970-01-01
    • 2021-11-18
    相关资源
    最近更新 更多