C++ 编译器优化了我的 bool 检查函数
c++ compiler optimized out my bool check function
我在做JNI开发的时候发现了一些有线的结果。
这是我的测试代码:
#include <stdint.h>
#include <iostream>
#define JNI_FALSE 0
#define JNI_TRUE 1
typedef uint8_t jboolean;
inline jboolean bool2jboolean( bool b ) { return b ? JNI_TRUE : JNI_FALSE; }
void test(bool *b) {
volatile jboolean t = bool2jboolean(*b);
std::cout << "t as int: " << (int)t << std::endl;
std::cout << "t as bool : " << (bool)t << std::endl;
std::cout << "t as jboolean(char) : " << t << std::endl;
}
int main() {
int a = 105;
test((bool*)&a);
return 0;
}
-O0 工作正常,但如果我打开 -O3,似乎 bool2jboolean 不知何故被编译器优化了:
$ clang++ -O0 test.cpp
$ ./a.out
t as int: 1
t as bool : 1
t as jboolean(char) :
$ clang++ -O3 test.cpp
$ ./a.out
t as int: 105
t as bool : 1
t as jboolean(char) : i
我不确定这里发生了什么。
编译器是否假定布尔值始终为 0 或 1?
(所以 bool2jboolean 变成了一个无意义的函数?)
在我的 mac 上测试:
$ clang++ -v
Apple clang version 12.0.0 (clang-1200.0.32.28)
Target: x86_64-apple-darwin19.6.0
Thread model: posix
InstalledDir: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin
编辑:
我们不能确保 *b 指向一个“规范化”的 bool(例如,一个底层有随机值的未初始化的 bool),这也是我们引入 bool2jboolean 的原因,希望它可以“检查”bool 和 return 一个验证 jboolean。 (否则,如果 t 不是 0 或 1,JNI 运行时将抛出。)
但似乎 bool2jboolean 完全没有做到这一点。
我们如何检查未初始化的布尔值? (按位运算是剩下的唯一方法了??)
你故意欺骗编译器,告诉它 a
的内存是合法的 bool
,尽管 C++ 明确表示 bool
只能是 true
或 false
。高度优化下的编译器发现true
是1
,false
是0
,分别匹配JNI_TRUE
和JNI_FALSE
,所以它可以直接复制 bool
的值,而不是进行条件选择。由于 bool
“不能”是其他任何东西,因此这是一个安全的优化。你对编译器说谎使它变得不安全。
如果你只是完成了:
int main() {
int a = 105;
bool b = a;
test(&b);
return 0;
}
它会起作用,因为对 bool
的赋值标准化为 true
(对于非零值)和 false
(对于零值),而没有 假设 值已经是 true
或 false
。相比之下,您的演员表改变了在没有规范化的情况下解释内存的方式(您断言指向的内存已经已经以正常形式)。
我在做JNI开发的时候发现了一些有线的结果。
这是我的测试代码:
#include <stdint.h>
#include <iostream>
#define JNI_FALSE 0
#define JNI_TRUE 1
typedef uint8_t jboolean;
inline jboolean bool2jboolean( bool b ) { return b ? JNI_TRUE : JNI_FALSE; }
void test(bool *b) {
volatile jboolean t = bool2jboolean(*b);
std::cout << "t as int: " << (int)t << std::endl;
std::cout << "t as bool : " << (bool)t << std::endl;
std::cout << "t as jboolean(char) : " << t << std::endl;
}
int main() {
int a = 105;
test((bool*)&a);
return 0;
}
-O0 工作正常,但如果我打开 -O3,似乎 bool2jboolean 不知何故被编译器优化了:
$ clang++ -O0 test.cpp
$ ./a.out
t as int: 1
t as bool : 1
t as jboolean(char) :
$ clang++ -O3 test.cpp
$ ./a.out
t as int: 105
t as bool : 1
t as jboolean(char) : i
我不确定这里发生了什么。
编译器是否假定布尔值始终为 0 或 1?
(所以 bool2jboolean 变成了一个无意义的函数?)
在我的 mac 上测试:
$ clang++ -v
Apple clang version 12.0.0 (clang-1200.0.32.28)
Target: x86_64-apple-darwin19.6.0
Thread model: posix
InstalledDir: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin
编辑: 我们不能确保 *b 指向一个“规范化”的 bool(例如,一个底层有随机值的未初始化的 bool),这也是我们引入 bool2jboolean 的原因,希望它可以“检查”bool 和 return 一个验证 jboolean。 (否则,如果 t 不是 0 或 1,JNI 运行时将抛出。)
但似乎 bool2jboolean 完全没有做到这一点。
我们如何检查未初始化的布尔值? (按位运算是剩下的唯一方法了??)
你故意欺骗编译器,告诉它 a
的内存是合法的 bool
,尽管 C++ 明确表示 bool
只能是 true
或 false
。高度优化下的编译器发现true
是1
,false
是0
,分别匹配JNI_TRUE
和JNI_FALSE
,所以它可以直接复制 bool
的值,而不是进行条件选择。由于 bool
“不能”是其他任何东西,因此这是一个安全的优化。你对编译器说谎使它变得不安全。
如果你只是完成了:
int main() {
int a = 105;
bool b = a;
test(&b);
return 0;
}
它会起作用,因为对 bool
的赋值标准化为 true
(对于非零值)和 false
(对于零值),而没有 假设 值已经是 true
或 false
。相比之下,您的演员表改变了在没有规范化的情况下解释内存的方式(您断言指向的内存已经已经以正常形式)。