我正在开发一个微控制器项目,其中有一组来自通信接口的无符号整数。为方便起见,可以通过定义宏访问它们。
我需要发送一些无符号长值,而不是必须处理来自通信寄存器的两个值并将它们转移到辅助长寄存器,我可以安全地使用指针并从数组中读出两个值一次。
我对此感兴趣,因为控制器上的处理资源非常有限。这样安全吗,数组值在内存中是否总是连续的?
示例代码
...
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
答案 0 :(得分: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)
答案 1 :(得分:2)
根据标准,你有一个别名错误,任何事情都可能发生。
允许编译器假设16位int
和32位long
类型之间没有别名,并且您可能会得到令人惊讶的行为(没有警告),因为您违反了该合同。 / p>
说不,使用位移来从两个long
组成你的int
,并依靠编译器为你优化它(它不应该真正使用位移-the-罩)。您可能希望查看程序集以确定它是否失败。
6.5表达式§7
对象的存储值只能由具有其中一个的左值表达式访问 以下类型:88)
- 与对象的有效类型兼容的类型,
- 与对象的有效类型兼容的类型的限定版本,
- 与对象的有效类型对应的有符号或无符号类型的类型,
- 对应于对象有效类型的限定版本的有符号或无符号类型的类型,
- 在其成员中包含上述类型之一的聚合或联合类型(包括递归地,子聚合或包含联合的成员),或者
- 字符类型。
由于int
和long
不兼容,并且没有例外,因此禁止别名。
你的编译器越现代(并且越优化),它越松散就会咬你。
BTW:大多数编译器实现了许多方言,而GCC允许禁用-fno-strict-aliasing
的严格别名。请确保不要仅禁用警告,而是禁用实际的优化。
答案 2 :(得分:1)
如果您希望这样做,请相信您平台上的sizeof(int)*2==sizeof(long)
并且满足于此不可移植性(因为此假设是不可移植的)您可以(并且应该)使用工会移动两种类型之间以定义的方式来回传递。
union {
int in [2];
long out;
};
您可以在数组中存储此联合类型的元素,并将int
写入in
并从long
读取out
,或者您可以{ {1}}从int
数组进入联合,并一次读取两个int
。
请注意,如果您想要更多可移植性,可以使用<stdint.h>
中的整数类型:
long
然后,唯一与平台相关的行为将是:
答案 3 :(得分:0)
是的,这是安全的,有以下假设:
此数据的发件人正在按您的预期发送数据。例如,comms[2]
和comms[3]
实际上构成了unsigned long
值,正如您所期望的那样。
发送者的位顺序(称为endianness)和字节顺序是您所期望的。
答案 4 :(得分:0)
根据随后对该问题的评论,答案是否定的。我的原始答案解释了原因。
这取决于您是否需要完全安全且可移植的代码,或者是否可以使用特定体系结构的代码,以及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*)
演员表来尊重你。但不是在大端系统上。
答案 5 :(得分:0)
我认为将无符号int指针转换为无符号长指针将在MSP430上工作,因为MSP430是小端 AND MSP430不需要4字节长整数在4字节上对齐边界。但是不要指望在另一个平台上工作。
并且不要期望您也可以将两个连续字节转换为unsigned int。 MSP430要求必须在偶数地址上对齐2字节字。因此,如果第一个字节恰好位于奇数地址,那么当您将其转换为单词时,您将得到未定义的行为。