[英]Reading two ints from an array as a long
我正在一个微控制器项目中,其中有一个来自通信接口的无符号整数数组。 为了方便起见,可以通过define宏访问它们。
我需要发送一些无符号的长值,而不是必须从comms寄存器中处理两个值并将它们移入辅助长寄存器中,对我而言,使用指针一次从数组中读取两个值是否安全?
我对此感兴趣,因为控制器上的处理资源非常有限。 这样安全吗,数组值在内存中总是连续的吗?
范例程式码
...
unsigned int comms[MAX_ADDRESS];
...
#define FOO comms[0]
#define BAR comms[1]
#define VAL_1 comms[2]
#define VAL_1_EXT (*(unsigned long*)(&comms[2])) // Use pointer trickery to read a long
#define VAL_2 comms[4]
#define VAL_2_EXT (*(unsigned long*)(&comms[4]))
...
不确定是否相关,但它是TI MSP430系列的芯片,编译器版本为TI 4.3.3
这取决于您所说的“安全”。 从某种意义上说,C标准对事件将不会发生什么是绝对不安全的,因为您使用指针强制转换来别名化类型。 这是不可携带的。
但是,非便携式并不意味着非功能性。 如果该代码不是供生产使用的,并且您对开发环境有很好的控制,则您的建议很可能会很好。 C标准确实保证数组元素是连续的。 如果编译器生成的代码可以从commo寄存器中提取两个(我想是)16位数据量,以在一个实例中正确形成32位长,那么实际上可以确定:
它将在所有用法中都这样做。
将来的编译器版本将执行相同的操作。
没有保证,但是实际上这是一个合理的选择。
要了解所获取的代码是否正确,请使用-S
编译并检查。 写一个好的测试来验证。
无论如何,您已经通过隔离宏中的访问代码采取了一种好的方法(尽管您应该在末尾删除分号)。
下面的宏相对于C标准是定义明确的。
#define VAL_1_EXT (((unsigned long)comms[3] << 16) | (unsigned long)comms[2])
如果你写
unsigned long x = VAL_1_EXT;
一个好的优化编译器应该使用上面的宏生成与您提议的宏相同的代码。 我猜您是在说它不是一个好的优化编译器。
正如注释中指出的那样,该宏不是l值。 您不能分配给它。 为此,您需要一个单独的宏。
#define SET_VAL_1_EXT(Val) do { \
unsigned long x = (unsigned long)Val;
comms[2] = x; \
comms[3] = (unsigned)(x >> 16); \
} while (0)
根据标准,您有一个别名错误,任何事情都可能发生。
允许编译器假定16位int
和32位long
类型之间没有别名,并且您可能会得到令人惊讶的行为(无警告),因为您违反了该约定。
只需说不,就可以使用移位将两个int
组成的long
组成,并依靠编译器为您优化这一点(它实际上不应在后台使用移位)。 您可能需要查看程序集以确定它是否失败。
6.5表达式§7
一个对象只能通过具有以下类型之一的左值表达式访问其存储值:88)
—与对象的有效类型兼容的类型,
—与对象的有效类型兼容的类型的限定版本,
—一个类型,它是与对象的有效类型相对应的有符号或无符号类型,
—一种类型,是与对象的有效类型的限定版本相对应的有符号或无符号类型,
—集合或联合类型,其成员中包括上述类型之一(递归地包括子集合或包含的联合的成员),或
—字符类型。
由于int
和long
不兼容,并且也不例外,因此禁止别名。
您的编译器越现代(并且越擅长优化),则其发挥作用的可能性就越大。
顺便说一句:大多数编译器实现许多方言,并且GCC允许使用-fno-strict-aliasing
禁用-fno-strict-aliasing
。 确保不仅禁用警告,而且要进行实际的优化。
如果您希望这样做,请确信平台上的sizeof(int)*2==sizeof(long)
,并且对这种不可移植性感到满意(因为这种假设不可移植),您可以(并且应该)使用以定义的方式在两种类型之间来回移动的联合。
union {
int in [2];
long out;
};
您可以将这种并集类型的元素存储在数组中,然后将int
s写入in
并从out
读取long
s,也可以将int
s从int
数组中放入union中,并一次读取两个long
。
请注意,如果要提高可移植性,可以使用<stdint.h>
的整数类型:
union {
int32_t in [2];
int64_t out;
};
然后,唯一依赖于平台的行为将是:
是的,基于以下假设,这是安全的:
此数据的发送者正在按预期发送数据。 例如, comms[2]
和comms[3]
实际上确实组成了一个unsigned long
值,正如您所期望的那样。
发件人的位顺序(称为endianness )和字节顺序是您所期望的。
根据对问题的后续评论,答案是否定的。 我的原始答案解释了原因。
这取决于您是否想要完全安全且可移植的代码,或者是否适合特定体系结构的代码以及int
的字节序和顺序。
如果您可以使用特定代码,则...
C中的数组始终是连续的内存位置,并且始终打包,并且很多代码都依赖于此。
在大端序系统上,如果您有int
顺序
high-int,low-int
每个int
是
high-byte,low-byte
并且内存中的字节是
high-int-high,high-int-low,low-int-high,low-int-low
然后可以使用(long int*)
强制转换。 但不是在小端系统上。
在小端系统上,如果您有int
顺序
low-int,high-int
每个int
是
low-byte,high-byte
内存中的字节是
low-int-low,low-int-high,high-int-low,high-int-high
然后可以使用(long int*)
强制转换。 但不是在大型字节序系统上。
我相信将无符号的int指针强制转换为无符号的长指针将在MSP430上起作用,因为MSP430是低位字节序的,并且MSP430不需要4字节长在4字节边界上对齐。 但是不要指望在其他平台上可以正常工作。
并且不要期望您也可以将两个连续的字节转换为一个无符号的int。 MSP430要求2字节字必须在偶数地址上对齐。 因此,如果第一个字节恰好位于奇数地址,则将其转换为一个单词时,您将获得不确定的行为。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.