C: 在 sizeof() 中转换数组时出现意外结果
C: Unexpected result when cast'ing an array within sizeof()
我不得不经历一个(另一个)——对我来说——意想不到的 C 行为,这次是 sizeof
。
我的目标是试图理解这种行为背后的原因以及我应该如何解决这个问题。我对替代解决方案并不感兴趣,因为我的主要目标是了解这里发生的事情以及原因。
我有一个通过 #define
-C 预处理器宏 (#define CONST "foobar"
) 定义的字符串,并在以下函数中使用它:
senddata(uint8_t * data, uint32_t len)
.
因为 - 取决于 implementation/architecture,但至少在 x86 上 - 默认情况下已对 char 进行签名,我在以这种方式调用它时收到有关 "differ[ence] in signedness" 的警告:
senddata(CONST, sizeof(CONST))
.
所以我必须在每次 senddata
调用时强制转换它 (senddata((uint8_t *)CONST, sizeof(CONST))
)。
因为我的代码中对 CONST 的所有使用实际上都将转换为 uint8_t
,所以我想我将只更改定义:
#define CONST "foobar"
-> #define CONST ((uint8_t *)"foobar")
并且不必再为进一步的铸造而烦恼。
虽然这确实消除了警告并且一切看起来都很好,但我不得不学习困难的方法,在那些情况下 sizeof() 实际上不再是 return 字符串的长度,而是数据类型,在本例中为 uint8_t *
.
对我来说这一点都不明显。
所以我的问题是2折:
- 1) 在上述情况下,我该如何正确执行?
- 2)为什么会这样?
- 3) 我怎么会知道这件事?这并不是说(被动)攻击性的,而是:根据什么先验知识我应该能够得出这样的结论是行不通的?
我在某个地方学到的一些知识可能会影响到这一点,但我不太确定该怎么做:sizeof() 不是一个普通函数,而是一个运算符(例如 sizeof int
没有括号的作品)。
我的另一个推测:"foobar"
是一个字符数组,而 (char *)"foobar"
是一个指针。
你就快完成了,你需要记住的是,sizeof
作用于操作数的类型,而不是值。
引用 C11
,章节 §6.5.3.4
The sizeof
operator yields the size (in bytes) of its operand, which may be an
expression or the parenthesized name of a type. The size is determined from the type of
the operand. The result is an integer. If the type of the operand is a variable length array
type, the operand is evaluated; otherwise, the operand is not evaluated and the result is an
integer constant.
例如,sizeof ("array")
与 sizeof (char [6])
相同,因为 "array"
是 char[6]
类型。鉴于 char
的大小定义为 1,它将产生结果 6.
但是,当您对 sizeof
的操作数使用强制转换时,它会将强制转换视为上述定义的类型。因此,sizeof ((char*)"array")
与 sizeof (char*)
相同。根据您的平台,它可以产生 4
或 8
的值,指针的大小(指向 char
)。
您正在将 CONST
- 一个充当数组 const char CONST[]
的字符串文字 - 转换为 整数 。在这种情况下,数组会退化为指针,因此您基本上是 将一个指向字符串的指针转换为一个整数 。这不是你想要的。你的 send
不会做任何明智的事情,除非你的架构有 8 位长的指针(那是 1 字节,而不是 8 字节!)。
您真正想要的是以下用于发送的签名 - 注意它也是正确的常量:
void send(const void *, size_t);
然后你需要一个辅助宏:
#define send_lit(literal) send(literal "", sizeof(literal))
如果您尝试使用除字符串文字以外的任何内容来调用它,这将失败,因此它相对来说是万无一失的。
完整示例:
#include <stdio.h>
#include <stdint.h>
#define send_lit(literal) send(literal "", sizeof(literal))
inline void send_byte(uint8_t byte) {
// just an example implementation
printf("%c", (char)byte);
}
void send(const void *src, size_t size) {
const char *p = (const char *)src;
while (size)
send_byte((uint8_t)*p++);
}
int main() {
send_lit("Hello, world!\n");
#if 0
const char* p;
send_lit(p); // won't compile
#endif
}
我不得不经历一个(另一个)——对我来说——意想不到的 C 行为,这次是 sizeof
。
我的目标是试图理解这种行为背后的原因以及我应该如何解决这个问题。我对替代解决方案并不感兴趣,因为我的主要目标是了解这里发生的事情以及原因。
我有一个通过 #define
-C 预处理器宏 (#define CONST "foobar"
) 定义的字符串,并在以下函数中使用它:
senddata(uint8_t * data, uint32_t len)
.
因为 - 取决于 implementation/architecture,但至少在 x86 上 - 默认情况下已对 char 进行签名,我在以这种方式调用它时收到有关 "differ[ence] in signedness" 的警告:
senddata(CONST, sizeof(CONST))
.
所以我必须在每次 senddata
调用时强制转换它 (senddata((uint8_t *)CONST, sizeof(CONST))
)。
因为我的代码中对 CONST 的所有使用实际上都将转换为 uint8_t
,所以我想我将只更改定义:
#define CONST "foobar"
-> #define CONST ((uint8_t *)"foobar")
并且不必再为进一步的铸造而烦恼。
虽然这确实消除了警告并且一切看起来都很好,但我不得不学习困难的方法,在那些情况下 sizeof() 实际上不再是 return 字符串的长度,而是数据类型,在本例中为 uint8_t *
.
对我来说这一点都不明显。
所以我的问题是2折:
- 1) 在上述情况下,我该如何正确执行?
- 2)为什么会这样?
- 3) 我怎么会知道这件事?这并不是说(被动)攻击性的,而是:根据什么先验知识我应该能够得出这样的结论是行不通的?
我在某个地方学到的一些知识可能会影响到这一点,但我不太确定该怎么做:sizeof() 不是一个普通函数,而是一个运算符(例如 sizeof int
没有括号的作品)。
我的另一个推测:"foobar"
是一个字符数组,而 (char *)"foobar"
是一个指针。
你就快完成了,你需要记住的是,sizeof
作用于操作数的类型,而不是值。
引用 C11
,章节 §6.5.3.4
The
sizeof
operator yields the size (in bytes) of its operand, which may be an expression or the parenthesized name of a type. The size is determined from the type of the operand. The result is an integer. If the type of the operand is a variable length array type, the operand is evaluated; otherwise, the operand is not evaluated and the result is an integer constant.
例如,sizeof ("array")
与 sizeof (char [6])
相同,因为 "array"
是 char[6]
类型。鉴于 char
的大小定义为 1,它将产生结果 6.
但是,当您对 sizeof
的操作数使用强制转换时,它会将强制转换视为上述定义的类型。因此,sizeof ((char*)"array")
与 sizeof (char*)
相同。根据您的平台,它可以产生 4
或 8
的值,指针的大小(指向 char
)。
您正在将 CONST
- 一个充当数组 const char CONST[]
的字符串文字 - 转换为 整数 。在这种情况下,数组会退化为指针,因此您基本上是 将一个指向字符串的指针转换为一个整数 。这不是你想要的。你的 send
不会做任何明智的事情,除非你的架构有 8 位长的指针(那是 1 字节,而不是 8 字节!)。
您真正想要的是以下用于发送的签名 - 注意它也是正确的常量:
void send(const void *, size_t);
然后你需要一个辅助宏:
#define send_lit(literal) send(literal "", sizeof(literal))
如果您尝试使用除字符串文字以外的任何内容来调用它,这将失败,因此它相对来说是万无一失的。
完整示例:
#include <stdio.h>
#include <stdint.h>
#define send_lit(literal) send(literal "", sizeof(literal))
inline void send_byte(uint8_t byte) {
// just an example implementation
printf("%c", (char)byte);
}
void send(const void *src, size_t size) {
const char *p = (const char *)src;
while (size)
send_byte((uint8_t)*p++);
}
int main() {
send_lit("Hello, world!\n");
#if 0
const char* p;
send_lit(p); // won't compile
#endif
}