Re: [分享] Boolean in C/C++

作者: sdarktemplar (惟ゆい)   2020-02-10 12:12:00
請問一下,關於實際記憶體的使用上的問題
在寫MCU FW的時候會常有很多boolean值要存
當然如果mcu記憶體夠的話就用一個byte來放一個boolean
但是我個人會習慣寫成bitwise搭配mask的方式
這樣只要用原本1/8的記憶體用量
但是就是麻煩了點
現在的IDE跟compiler有辦法自動把8個bool宣告的變數佔的記憶體合併成一個byte嗎?
也就是說compiler自動幫我們做bitwise這件事情來節省記憶體
謝謝
※ 引述《Feis (永遠睡不著 @@)》之銘言:
: 前面幾篇有提到 bool 的問題。今天突然失眠,心血來潮,來分享一下 bool 的故事。
: 當然內容算是比較基礎而有許多主觀認知的,有什麼遺漏錯誤還煩請指正。
: [關於 Boolean (布林)]
: Boolean (布林) 作為一種資料型別 (data type) 時只具有兩種值: 『真』(true) 與
: 『偽』(false)。
: Boolean 可以進行的運算包含:而且 (and)、或 (or) 與非 (not) ,還有由這三種基本
: 運算延伸出來的各式邏輯運算。
: 基本上, Boolean 是不能做一般我們認為的算術運算 (+, -, *, /, ...) 的。
: Boolean 在一般程式語言中最重要的角色是作為『流程控制』的條件判斷。
: 在 C/C++ 裡面,if、for 和 while 等流程控制都仰賴於條件的 Boolean 運算結果。
: [在 C89 裡面的 Boolean]
: 雖然 Boolean 對於『流程控制』具有巨大的重要性,但在 C89 內並沒有直接替
: Boolean 設定一個專有的資料型別。
: 在 C89 中 Boolean 是隱性地跨越多個型別、運算以及語法中實現。
: 首先,C89 替內建型別所具有的 Boolean 值做了規定:
: * 數值型別的 Boolean 值:0 的值為『偽』,其他的值為『真』。
: * 指標型別的 Boolean 值:null 的值為『偽』,其他的值為『真』
: (剛好 0 當指標值時是代表 null 指標)
: if (0) {
: printf("True"); // 不會印 True.
: }
: if (0.1) {
: printf("True"); // 會印 True.
: }
: 這個決定讓我們不必產生一個 Boolean 的專有型別,因為內建型別都可以當做
: Boolean 用。
: 而且, 0 為 『偽』,非 0 為『真』也是一種容易記誦的方式, 使人不由得敬佩設計者
: 的巧思。
: 再來,C89 規定了關係 (>, <, ==, ...) 與邏輯 (&&, ||, ...) 等運算的結果值。 當
: 結果為『真』時會算出 int 型別的 1,而當結果為『偽』時會算出 int 型別的 0。
: 將上面稍微整理一下,C89 共做了:
: 1. 判斷內建型別的 Boolean 值時,將 0 作為『偽』而非 0 作為『真』。
: 2. 運算結果為『偽』時算出 int 的 0,而為『真』時算出 int 的 1。
: 以上這兩點造成我們容易覺得 Boolean 在 C89 裡面就是 int,但這是一個常見的誤解。
: 什麼意思呢?
: 如果我們寫成:
: typedef int bool; // 把 bool 定義成 int
: bool a = 0.1;
: if (a) {
: printf("True"); // 此時不會印出 True,因為 a 的值為 0 (偽)
: }
: 將 0.1 轉型成 int 會是 0,但是將 0.1 解釋成 Boolean 時因為是非 0 值所以應該要
: 是『真』(1)。
: 所以 int 是不能直接替代 Boolean 的。
: 此外,C89 並沒有替 Boolean 提供專用的型別還有很多缺點:
: 因為 Boolean 運算的結果是用 int 的 1 與 0 來表示真偽,使得將 Boolean 運算結果
: 作為整數運算的技巧大量地應用在 C89 以前的程式碼裡,因此產生了一些程式碼不直觀
: 的惡夢。例如:
: if (0 == 0 == 0) {
: printf("True"); // 不會印 True
: }
: if (4 > 3 > 2) {
: printf("True"); // 不會印 True
: }
: 如果再搭配了逐位元運算, 儲存 Boolean 所需要的空間大小可以由 int 再濃縮
: 成 1 bit,使得 Boolean 在 C89 中達到使用空間的大幅縮減。
: 但以上兩個技巧遺留下了大量無法撼動要依賴 Boolean 值是 1 或 0 的程式碼。
: [在 C++98 中的 Boolean]
: 作為一個新的語言,C++98 選擇了一個比較直接的決定:
: * 將 bool 作為 Boolean 型別。
: * 將 true 與 false 作為 bool 的字面常數。
: * 當運算結果為『真』時,算出 bool 型別的 true。
: * 當運算結果為『偽』時,算出 bool 型別的 false。
: 乍看之下似乎得到了解脫,因為我們有了表示 Boolean 的 bool 型別了。
: 不幸的是,為了跟大量原有的 C 程式碼相容,使得 C++98 面臨到一些困難:
: 1. 需要引入三個新的保留字: bool 、 true 和 false 。 使得與原有的 C 程式碼可能
: 會造成名稱衝突。
: (C99 不這麼做,後面會提)
: 2. 因為假設 Boolean 為『真』時算出 1 且為『偽』時算出 0 的原有 C 程式碼太多,
: C++98 只好允許 true 可以被隱性轉型成 int 的 1 ,而 false 可以被隱性轉型成
: int 的 0 。此外,數值或指標型別也都要能隱性轉成 true 或 false 以符合在 C89
: 裡面的用法。
: 這樣的作法使得 bool 用起來跟整數型別相似,造成我們之前的惡夢還是存在 (可以
: 參考與 Java 的差異),而這對於要求型別安全的 C++ 來說更是恐怖。
: (C/C++ 各版本都有的問題)
: 3. 為了得到以前使用位元運算將 Boolean 表示為 1 bit 的效用可以直接使用在 bool
: 上,C++98 對 vector<bool> 做了特製化,使得每個 bool 在這 vector 中都只耗用
: 1 bit,但也產生了使用上的問題或多執行緒的惡夢。
: (C++ 各版本都有的問題)
: 4. 有些時候,我們想將物件作為流程控制或邏輯運算的條件,並依照物件的狀態決定流
: 程的進行。
: 例如:
: Object a;
: if (a) {
: printf("True"); // 如果 a 物件符合期望就印
: }
: 因此我們需要讓物件可以轉型成 bool。但是讓物件可以隱性轉型成 bool 的風險太
: 大,因為這意味著物件可以隱性成整數型別。
: 設計師為了避免將一般物件被誤當成整數用,原本想讓物件可以隱性轉型成 bool 型
: 別只好改為轉型成指標型別。
: 例如: std::basic_ios::operator void *() const;
: (C++11 有部分解決, 後面會提)
: [C99 的 Boolean]
: 為了跟 C89 的相容性, C99 並沒有選擇跟 C++98 一樣的道路去引進三個新的保留字,
: 而是:
: * 將 _Bool 作為 Boolean 的型別,
: * 在 stdbool.h 中設定了四個 macro:
: - 將 bool 定義為 _Bool,
: - 將 true 定義為 1,
: - 將 false 定義為 0,
: - 將 __bool_true_false_are_defined 定義為 1。
: 引入了叫 _Bool 的保留字作為 Boolean 的型別名稱。因為名字太奇怪, 不容易跟原有
: C 程式碼撞名,而不會遇到跟 C++98 一樣的窘況。
: 將 bool, true 和 false 設計為 macro,使得我們在程式碼中可以有條件的選擇是否用
: 這三個名稱。 也就是說,如果你不 #include <stdbool.h> 的話,bool 、 true 和
: false 預設是未定義的。這樣可以避免跟原有程式碼撞名,或有必要時做一些處理。
: 此外,你可以發現,因為是使用 macro,Boolean 運算的結果依然是 int 型態,這點使
: 得一般的運算結果跟 C89 是一致的。
: 換句話說,C99 解決 C89 問題的作法是提供的一個真正的 Boolean 型別, 但是其他都
: 沒有真的改。而 _Bool 的用途主要是讓剛剛錯誤的程式碼正常執行:
: _Bool a = 0.1; // a 會是『真』而不是 0
: if (a) {
: printf("True"); // 會印出 True
: }
: [C++11 的 Boolean]
: C++11 的 bool 大致上依循 C++98 的腳步,但多了一點調整:
: 之前我們不敢讓物件隱性轉型成 bool ,因為怕被誤當做整數用。但是現在我們可以用
: C++11 的 explicit cast operator 來讓物件只能顯性轉型成 bool。
: explicit cast operator 的意思是,原本我們在類別裡面自定的轉型運算子不能被指定
: 為只提供顯性轉型。也就是:
: class Object {
: operator bool() const; // 轉型成 bool 的運算子
: };
: 提供上面的轉型運算子意味著:
: Object a;
: a + 1; // 是合法的,a 會被隱性轉型成 bool 後轉型成 int 運算
: 這樣並不好,讓物件可以當整數用簡直是惡夢!
: 我們可以使用 C++11 提供的 explicit cast operator 規定我們使用轉型運算子時需要
: 是顯性的:
: class Object {
: explicit operator bool() const;
: };
: 如果對這個類別使用之前的程式碼:
: Object a;
: a + 1; // 編譯失敗,因為 a 無法隱性轉型成 bool 或整數
: 當我們真的需要將 a 轉型成 bool 時需要使用顯性轉型:
: Object a;
: if ((bool)a) { // 顯性轉型成功
: printf("True");
: }
: 但是這樣用起來又有點麻煩,所以 C++11 讓流程控制或邏輯運算時的條件具有顯性轉成
: bool 的語意 (其他時候則不會)。
: 換句話說,在 C++11 裡面,
: Object a;
: if (a) {
: printf("True");
: }
: 會自動解釋成
: Object a;
: if ((bool)a) {
: printf("True");
: }
: 這樣就達到讓物件要轉型成 bool 需要是顯性的, 但是使用在邏輯運算或做流程控制
: 時,又不需要一直寫顯性轉型的程式碼。
: 但是其他的時候,因為 a 不能被隱性轉型成 bool,而得到更多的型別安全。
作者: KaryuuIssen (一閃)   2020-02-10 12:29:00
當然不會 組合語言的定址都是以byte為單位的
作者: ctrlbreak   2020-02-10 17:01:00
http://bit.ly/39nhiiF 這種用法呢?
作者: adrianshum (Alien)   2020-02-11 09:01:00
C++ 的話用 bitset 不就好了?
作者: sdarktemplar (惟ゆい)   2020-02-11 13:41:00
to 3F,主要是問C的部分to 2F,這個方式我研究一下,謝謝
作者: CoNsTaR ((const *))   2020-02-18 13:33:00
gcc 的話enum __attribute__((packed)) Boolean { TRUE, FALSE };

Links booklink

Contact Us: admin [ a t ] ucptt.com