3

在看的时候:

C 编译器可以在结构中的第一个元素之前添加填充吗?

我想出了以下代码:(
忽略此示例中未释放内存的事实。)

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

typedef struct {
    char *cstr;
    size_t len;
} str_t;

void setStr(str_t* dest, const char* src)
{
    size_t len = strlen(src);
    dest->cstr = malloc(len + 1);
    dest->len = len;
    memcpy(dest->cstr, src, len + 1);
}

int main(void)
{
    str_t str;
    setStr(&str, "woot!");
    printf("%s\n", str);
    return 0;
}

令人惊讶的是,这确实有效。这个电话:

printf("%s\n", str);

似乎相当于这个:

printf("%s\n", str.cstr);

所以有人会认为以下也是可能的:

char* plainstr = malloc(str.len + 1);
strcpy(plainstr, str);

但是不行。相反,printf,strcpy不是可变参数,所以有类型检查。编译器理所当然地抱怨:

passing 'str_t' to parameter of incompatible type 'const char *'

但是试图通过强制转换来告诉编译器“我是认真的”:

strcpy(plainstr, (const char*)str);

也不行:

operand of type 'str_t' where arithmetic or pointer type is required

请注意,以下内容不起作用:

strcpy(plainstr, (const char*)&str);

由于str.cstr != &str. 例如,这个的输出:

printf("%p %p\n", str.cstr, &str);

是否如下:

0xbdb010 0x7fff788f6ab8

事实上,垃圾数据正在被复制到plainstr.

所以问题是:

  1. 为什么不允许将结构转换为指针类型?
  2. printf如果不允许投射,怎么能正确处理这个问题?
4

1 回答 1

2

为什么不允许将结构转换为指针类型?

因为没有意义。您如何将一大堆可能不相关的不同类型的信息重新解释为简洁的内存地址?但是,在您提出的上一个问题中,所有回答的人都引用了 C 标准,并且标准中的一个特定声明指出

结构的地址是它的第一个元素的地址

所以(正如@Mat 已经指出的那样),你确实可以

strcpy(destination, *(const char **)&str);

由于我刚才列举的原因,这“会起作用”。

如果不允许强制转换,printf 如何正确处理这个问题?

因为在 C 语言中,类型转换通常只是为了愚弄编译器(除非不是这样)。通过传递结构,结构将被复制,您的堆栈将类似于(为了简单起见,我有意省略结构中的任何填充):

> top of the stack: pointer to the format string
> address of the copied struct *and*  address of the copy of the char pointer
> address of the length of the string (size_t)
> every other stuff

所以,现在printf()要做的是:

  • 从堆栈中弹出第一个值。它将是格式字符串。
  • 现在当它在格式字符串中遇到%s格式说明符时,它会弹出另一个 char 指针——实际上,它是指向结构的指针,也是指向第一个元素的指针,也就是要打印的字符串。
  • 所以它会愉快地打印字符串并返回。

此外,这仍然是未定义的行为,尽管它有效 - 如果您没有指定格式字符串,printf()它实际上对应于您作为其可变参数传入的类型,那么这不符合要求,您可以期待任何事情发生。

于 2012-11-03T15:58:03.810 回答