prev up next   top/contents search

comp.lang.c FAQ 列表· 问题 2.6

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.1216.7)变得很重要,并且必须予以保留。

C99 引入了 柔性数组成员 的概念,它允许在结构体的最后一个成员是数组时省略其大小,从而提供了一个明确定义的解决方案。

参考:Rationale Sec. 3.5.4.2
C9X Sec. 6.5.2.1


prev up next   contents search
关于此 FAQ 列表   关于 Eskimo   搜索   反馈   版权

Eskimo North 托管