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