C語言如何定義出指定變數名稱且佔1bit的全局變數?
兩種方案,其他答主都已經提到過了,一種是位運算,輔之以一系列宏;還有一種是使用位段。
使用位運算 + 宏的方式,代碼比較難讀,但是對於某些操作,使用位運算代碼可以很簡潔。位段則是 C 語言提供的語法糖,本質上還是位運算,只不過編譯器幫你寫好表達式。使用位段的話,取值和賦值都比位運算的方式明了的多。但是題主又不想使用有點號的二級訪問方式,下面有題主回答了,用 C++ 里的類封裝,不過這樣就是一個 bit 的變數,佔一個 byte 了,那既然這樣為什麼不直接用原生的 bool 類型?
無論上面哪種方式,歸根到底還是把 1 byte 拆成 8 bit 用。這樣做是為了效率。支持主流機器架構的主流語言都是這樣搞的,你看 C++ 和 Java 的布爾類型,都是只用到 1 個 bit 就夠了,但卻還是佔了 1 byte 的存儲空間。我猜 ST 語言里,它所謂支持定義 1bit 的變數只是語法糖而已,本質上還是幾個 bit 變數擠一擠,再用位運算來操作每個 bit。如果不是這樣的話,這個語言給我的感覺就很非主流。
如果題主需要的是 C 語言和 ST 語言進行底層交互的話,那麼每個 bit 肯定得要固定在某個位置的,那麼用位運算去自己手動控制對齊。如果題主只是想把一種語言的代碼翻譯成另一種語言的話,直接用 bool 就好
原來的回答誤讀了題主的問題。
題主要把下面這段代碼翻譯成C代碼
VAR_GLOBAL
gvar AT %mb0.1 : bool;
gvar1 AT %mb0.2 : bool;
END_VAR
我來試一試。
union{
uint32_t mb0;
struct{
int gvar0:1;
int gvar1:1;
};
}gBitVar;
#define GLB_BIT_VAL(bName) gBitVar.bName
int main(void)
{
GLB_BIT_VAL(gvar0) = 1;
GLB_BIT_VAL(gvar1) = 0;
… …
… …
}
看上去友好一些了
在再理解一下位域結構的全局變數問題。
位域結構在全局變數中,如果沒有明確的結構變數名,是不支持的。全局變數必須要能產生確定的地址,在編譯時才能通過。而局部變數在編譯時,能以寄存器變數方式存在,所以會支持那種方式的位域結構。
2019.7.18
下面是原來的回答。
======================================
在ARM的Cortex-M3/M4上底層支持,而且是真正的實現bit位原子操作。
STM32F3/F4或是TM4C,MSP432都支持bit-banding。它將32MB的SRAM內存按Bit映射到一塊1MB的內存區域。
這意味著,你可以通過指定變數地址,來定義一個「bit型「的變數。雖然這個變數的類型應該是32位整型,但這個變數的地址的確只代表一個bit。
例如:
uint32_t bitVar __attribute__((at(0x22000000)));
//該變數bitVar只表示地址在0x2000000內存的bit-0位。
uint32_t bitArr[10] __attribute__((at(0x22000000)));
//定義了10個bit的數組。
&-->bit位原子操作對I/O密集型應用很有好處。
一般編譯器都不支持定義僅佔一個位的變數,因為內存的最小存取單元也不能達到1bit的粒度,如果讓每個1bit的變數都佔一組內存對其的位元組數,那這種設計就太不「優雅」了,也沒有什麼意義。因為如果你真想為每1bit賦與不同的「意義」,你可以使用位的與或非等操作來達到,而且這樣做還可以很「優雅」。如下面這段Linux內核中的代碼:
/*
* Structure for FS_IOC_FSGETXATTR[A] and FS_IOC_FSSETXATTR.
*/
struct fsxattr {
__u32 fsx_xflags; /* xflags field value (get/set) */
....
....
};
/*
* Flags for the fsx_xflags field
*/
#define FS_XFLAG_REALTIME 0x00000001 /* data in realtime volume */
#define FS_XFLAG_PREALLOC 0x00000002 /* preallocated file extents */
#define FS_XFLAG_IMMUTABLE 0x00000008 /* file cannot be modified */
#define FS_XFLAG_APPEND 0x00000010 /* all writes append */
#define FS_XFLAG_SYNC 0x00000020 /* all writes synchronous */
#define FS_XFLAG_NOATIME 0x00000040 /* do not update access time */
#define FS_XFLAG_NODUMP 0x00000080 /* do not include in backups */
#define FS_XFLAG_RTINHERIT 0x00000100 /* create with rt bit set */
#define FS_XFLAG_PROJINHERIT 0x00000200 /* create with parents projid */
#define FS_XFLAG_NOSYMLINKS 0x00000400 /* disallow symlink creation */
#define FS_XFLAG_EXTSIZE 0x00000800 /* extent size allocator hint */
#define FS_XFLAG_EXTSZINHERIT 0x00001000 /* inherit inode extent size */
#define FS_XFLAG_NODEFRAG 0x00002000 /* do not defragment */
#define FS_XFLAG_FILESTREAM 0x00004000 /* use filestream allocator */
#define FS_XFLAG_DAX 0x00008000 /* use DAX for IO */
#define FS_XFLAG_COWEXTSIZE 0x00010000 /* CoW extent size allocator hint */
#define FS_XFLAG_HASATTR 0x80000000 /* no DIFLAG for this */
/* Transfer xflags flags to internal */
static inline unsigned long ext4_xflags_to_iflags(__u32 xflags)
{
unsigned long iflags = 0;
if (xflags FS_XFLAG_SYNC)
iflags |= EXT4_SYNC_FL;
if (xflags FS_XFLAG_IMMUTABLE)
iflags |= EXT4_IMMUTABLE_FL;
if (xflags FS_XFLAG_APPEND)
iflags |= EXT4_APPEND_FL;
if (xflags FS_XFLAG_NODUMP)
iflags |= EXT4_NODUMP_FL;
if (xflags FS_XFLAG_NOATIME)
iflags |= EXT4_NOATIME_FL;
....
....
對於位的使用其實很簡單,一般是:
- 一般是先有一個變數,用於存儲每個位。如
unsigned int flags = 0;
- 然後定義上面變數中每個位的意思(不一定要用上所有位)。如(並不拘泥與此定義):
#define FLAG_A (1&<&<0)
#define FLAG_B (1&<&<1)
#define FLAG_C (1&<&<2)
#define FLAG_D (1&<&<3)
#define FLAG_E (1&<&<4)
#define FLAG_F (1&<&<5)
- 接著,使用位操作寫/讀每個位。如:
賦值/清除一個位:
flags |= FLAG_A; # set bit
flags = ~FLAG_A; # clear bit
賦值/清除多個位:
flags |= (FLAG_C | FLAG_E | FLAG_F); # set bits
flags = ~(FLAG_C | FLAG_E | FLAG_F); # clear bits
讀(判斷)一個位:
if (flags FLAG_A)
do_something();
讀(判斷)多個位中至少有一個位被置位:
if (flags (FLAG_A | FLAG_E))
do_something();
當然還有很多其它用法。在使用位操作時,一定要注意等號兩邊的變數/常量類型和邊界,以及可能有的符號位,避免在項目過大後出現難以調試的問題。
最後附上一個例子:
#include &
#define FLAG_A (1&<&<0) #define FLAG_B (1&<&<1) #define FLAG_C (1&<&<2) #define FLAG_D (1&<&<3) #define FLAG_E (1&<&<4) #define FLAG_F (1&<&<5) int main(int argc, char *argv[]) { unsigned int flags = 0; flags |= FLAG_A; printf("0x%x ", flags); flags = ~FLAG_A; printf("0x%x ", flags); flags |= (FLAG_C | FLAG_E | FLAG_F); printf("0x%x ", flags); if (flags FLAG_A) printf("Theres FLAG A "); else printf("Theres not FLAG A "); return 0; }
編譯執行:
# gcc -o mytest mytest.c -Wall
# ./mytest
0x1
0x0
0x34
Theres not FLAG A
更多內容請參閱:
醉卧沙場:README - 專業性文章及回答總索引
我的理解是你希望只定義一個變數,並且該變數只佔用1bit的內存空間,這是沒有辦法做到的,內存訪問地址最少也是8bit的整數倍,如果考慮內存對齊,在不同位寬的機器上整存整取造成的空間浪費也不少了。畢竟容量使用和間訪效率也是需要權衡的。
但是如果你有多個位變數,例如你有32個flag,而你希望只用4個byte(理論上4個byte剛好利用所有的bit),C語言支持位操作,你確實可以把多個位變數通過位操作merge到一個位元組長或者字長的變數中去。定義一個宏,把位先移位,然後或或者與就可以了。代碼很簡單,也可以寫得很規整,很多底層通訊協議就是這麼寫的,x86架構里有不少專用寄存器,像Flags寄存器,也就是PSW,也是這麼操作的。底層通訊協議這麼做是因為通訊成本高,處理成本低,充分利用各個位承載數據可以加速數據傳輸。
但是你要是知道的是,即便你用位操作實現了一個變數只佔1bit的空間內存,但是背後的訪問操作依然不是bit的級別的,更改或者獲取它的值比處理位元組變數開銷更大,因為你需要事先進行位運算。這就是所謂的處理效率和內存空間的權衡。
不同的平台不一樣 。
在 大部分的8bit單片機中, 支持這樣的變數定義。
但 標準C/C++中沒有, 只能 按結構體這種方式定義
或者是按位操作。
補充: 好多人說的 STM32的 位操作,嚴格來說不算是可定義變數,這些都是連接到不同的寄存器進行操作,不能自己使用
而8位機中,可以直接定義這樣的變數來使用,一般當做boolen。
例如 PIC單片機 中 ,可以用 bit a = 1 , b = 0;
推薦閱讀: