作为初学C程序员,我想知道,在设备中设置控制位的最佳易读和易于理解的解决方案是什么.有标准吗?任何模仿的示例代码?谷歌没有给出任何可靠的答案.
我看到的第一种方法是简单地设置所需的位.它在评论中需要一堆解释,似乎并不是那么专业.
DMA_base_ptr[DMA_CONTROL_OFFS] = 0b10001100;
Run Code Online (Sandbox Code Playgroud)
我看到的第二种方法是创建一个位域.我不确定这是否应该坚持,因为我从未遇到过以这种方式使用它(与我提到的第一个选项不同).
struct DMA_control_block_struct
{
unsigned int BYTE:1;
unsigned int HW:1;
// etc
} DMA_control_block_struct;
Run Code Online (Sandbox Code Playgroud)
其中一个选项比另一个更好吗?有没有我没看到的选择?
任何建议都将受到高度赞赏
dbu*_*ush 40
位字段的问题在于C标准没有规定它们的定义顺序与它们的实现顺序相同.所以你可能没有设置你认为你的位.
C标准第6.7.2.1.111节规定:
实现可以分配足够大的任何可寻址存储单元来保持位字段.如果剩余足够的空间,则紧跟在结构中的另一个位字段之后的位字段将被打包到相同单元的相邻位中. 如果剩余的空间不足,则是否将不适合的位域放入下一个单元或重叠相邻单元是实现定义的.单元内的位域分配顺序(高阶到低阶或低阶到高阶)是实现定义的. 未指定可寻址存储单元的对齐.
例如,struct iphdr
从Linux上的/usr/include/netinet/ip.h文件文件中查看代表IP头的定义:
struct iphdr
{
#if __BYTE_ORDER == __LITTLE_ENDIAN
unsigned int ihl:4;
unsigned int version:4;
#elif __BYTE_ORDER == __BIG_ENDIAN
unsigned int version:4;
unsigned int ihl:4;
#else
# error "Please fix <bits/endian.h>"
#endif
u_int8_t tos;
...
Run Code Online (Sandbox Code Playgroud)
您可以在此处看到,根据实现,位域的放置顺序不同.您也不应该使用此特定检查,因为此行为取决于系统.这个文件是可以接受的,因为它是系统的一部分.其他系统可以以不同方式实现这一点.
所以不要使用位域.
最好的方法是设置所需的位.但是,为每个位定义命名常量并对要设置的常量执行按位OR是有意义的.例如:
const uint8_t BIT_BYTE = 0x1;
const uint8_t BIT_HW = 0x2;
const uint8_t BIT_WORD = 0x4;
const uint8_t BIT_GO = 0x8;
const uint8_t BIT_I_EN = 0x10;
const uint8_t BIT_REEN = 0x20;
const uint8_t BIT_WEEN = 0x40;
const uint8_t BIT_LEEN = 0x80;
DMA_base_ptr[DMA_CONTROL_OFFS] = BIT_LEEN | BIT_GO | BIT_WORD;
Run Code Online (Sandbox Code Playgroud)
Ark*_*kku 19
其他答案已经涵盖了大部分内容,但值得一提的是,即使您不能使用非标准0b
语法,也可以使用shift来1
按位数将位移动到位,即:
#define DMA_BYTE (1U << 0)
#define DMA_HW (1U << 1)
#define DMA_WORD (1U << 2)
#define DMA_GO (1U << 3)
// …
Run Code Online (Sandbox Code Playgroud)
请注意最后一个数字如何与文档中的"位数"列匹配.
设置和清除位的用法不会改变:
#define DMA_CONTROL_REG DMA_base_ptr[DMA_CONTROL_OFFS]
DMA_CONTROL_REG |= DMA_HW | DMA_WORD; // set HW and WORD
DMA_CONTROL_REG &= ~(DMA_BYTE | DMA_GO); // clear BYTE and GO
Run Code Online (Sandbox Code Playgroud)
Ste*_*mit 18
老派的C方式是定义一堆位:
#define WORD 0x04
#define GO 0x08
#define I_EN 0x10
#define LEEN 0x80
Run Code Online (Sandbox Code Playgroud)
然后你的初始化成为
DMA_base_ptr[DMA_CONTROL_OFFS] = WORD | GO | LEEN;
Run Code Online (Sandbox Code Playgroud)
您可以使用|
以下方法设置各个位
DMA_base_ptr[DMA_CONTROL_OFFS] |= I_EN;
Run Code Online (Sandbox Code Playgroud)
您可以使用&
和清除单个位~
:
DMA_base_ptr[DMA_CONTROL_OFFS] &= ~GO;
Run Code Online (Sandbox Code Playgroud)
您可以使用&
以下方法测试各个位
if(DMA_base_ptr[DMA_CONTROL_OFFS] & WORD) ...
Run Code Online (Sandbox Code Playgroud)
但绝对不要使用位域.它们有它们的用途,但是当外部规范定义这些位在某些地方时,它们就没有了,正如我在这里假设的那样.
小智 8
位字段没有标准.在这种情况下,映射和位操作依赖于编译器.诸如二进制值0b0000
也不是标准化的.通常的做法是为每个位定义十六进制值.例如:
#define BYTE (0x01)
#define HW (0x02)
/*etc*/
Run Code Online (Sandbox Code Playgroud)
如果要设置位,可以使用:
DMA_base_ptr[DMA_CONTROL_OFFS] |= HW;
Run Code Online (Sandbox Code Playgroud)
或者你可以清除位:
DMA_base_ptr[DMA_CONTROL_OFFS] &= ~HW;
Run Code Online (Sandbox Code Playgroud)
现代C编译器可以很好地处理琐碎的内联函数-无需任何开销。我将使用所有抽象函数,以便用户无需操纵任何位或整数,并且不太可能滥用实现细节。
当然,您可以使用常量而不是函数来获取实现细节,但是API应该是函数。如果您使用的是古老的编译器,这还允许使用宏而不是函数。
例如:
#include <stdbool.h>
#include <stdint.h>
typedef union DmaBase {
volatile uint8_t u8[32];
} DmaBase;
static inline DmaBase *const dma1__base(void) { return (void*)0x12340000; }
// instead of DMA_CONTROL_OFFS
static inline volatile uint8_t *dma_CONTROL(DmaBase *base) { return &(base->u8[12]); }
// instead of constants etc
static inline uint8_t dma__BYTE(void) { return 0x01; }
inline bool dma_BYTE(DmaBase *base) { return *dma_CONTROL(base) & dma__BYTE(); }
inline void dma_set_BYTE(DmaBase *base, bool val) {
if (val) *dma_CONTROL(base) |= dma__BYTE();
else *dma_CONTROL(base) &= ~dma__BYTE();
}
inline bool dma1_BYTE(void) { return dma_BYTE(dma1__base()); }
inline void dma1_set_BYTE(bool val) { dma_set_BYTE(dma1__base(), val); }
Run Code Online (Sandbox Code Playgroud)
此类代码应由机器生成:我使用gsl
(成帧数为0)以模板和一些XML输入(基于寄存器详细信息)为基础生成代码。