Q我遇到了一些代码,其中结构体的声明如下:
struct name { int namelen; char namestr[1]; };然后进行了一些棘手的分配,使namestr数组表现得好像有几个元素,数量由namelen记录。这如何运作?这是合法的还是可移植的?
A尚不清楚这是否合法或可移植,但它非常流行。该技术的实现可能如下所示:
#include <stdlib.h> #include <string.h> struct name *makename(char *newname) { struct name *ret = malloc(sizeof(struct name)-1 + strlen(newname)+1); /* -1 for initial [1]; +1 for \0 */ if(ret != NULL) { ret->namelen = strlen(newname); strcpy(ret->namestr, newname); } return ret; }此函数为name结构体分配一个实例,并调整大小,以便namestr字段可以容纳请求的名称(不仅仅是一个字符,正如结构体声明所暗示的)。
尽管它很受欢迎,但这项技术也有些臭名昭著:Dennis Ritchie 称其为“与 C 实现的不必要的亲密关系”,官方解释认为它不完全符合 C 标准,尽管在所有已知实现中似乎都能正常工作。(仔细检查数组边界的编译器可能会发出警告。)
另一种可能性是将可变大小的元素声明得非常大,而不是非常小。上面的示例可以重写如下:
#include <stdlib.h> #include <string.h> #define MAXSIZE 100 struct name { int namelen; char namestr[MAXSIZE]; }; struct name *makename(char *newname) { struct name *ret = malloc(sizeof(struct name)-MAXSIZE+strlen(newname)+1); /* +1 for \0 */ if(ret != NULL) { ret->namelen = strlen(newname); strcpy(ret->namestr, newname); } return ret; }其中MAXSIZE大于将要存储的任何名称。然而,这种技术似乎也因为对标准的严格解释而无效。此外,这两种“亲密”结构都必须谨慎使用,因为程序员比编译器更了解它们的尺寸。
当然,为了真正安全,应该做的是使用字符指针而不是数组。
#include <stdlib.h> #include <string.h> struct name { int namelen; char *namep; }; struct name *makename(char *newname) { struct name *ret = malloc(sizeof(struct name)); if(ret != NULL) { ret->namelen = strlen(newname); ret->namep = malloc(ret->namelen + 1); if(ret->namep == NULL) { free(ret); return NULL; } strcpy(ret->namep, newname); } return ret; }(显然,“便利性”——将长度和字符串存储在同一内存块中——现在已经丢失,并且释放此结构的实例需要两次调用free;请参阅问题 7.23。)
当存储的数据类型是字符时,如上面的示例所示,可以将两次调用malloc合并为一次,以保持连续性(从而恢复使用一次调用free):
struct name *makename(char *newname) { char *buf = malloc(sizeof(struct name) + strlen(newname) + 1); struct name *ret = (struct name *)buf; ret->namelen = strlen(newname); ret->namep = buf + sizeof(struct name); strcpy(ret->namep, newname); return ret; }
However, piggybacking a second region onto a singlemalloccall like this is only portable if the second region is to be treated as an array ofchar。对于任何更大的类型,对齐(请参阅问题 2.12 和 16.7)变得很重要,并且必须予以保留。
C99 引入了 柔性数组成员 的概念,它允许在结构体的最后一个成员是数组时省略其大小,从而提供了一个明确定义的解决方案。
参考:Rationale Sec. 3.5.4.2
C9X Sec. 6.5.2.1