这是一个例子来说明我的问题,其中涉及一些我不能在这里发布的更复杂的代码.
#include <stdio.h>
int main()
{
int a = 0;
for (int i = 0; i < 3; i++)
{
printf("Hello\n");
a = a + 1000000000;
}
}
Run Code Online (Sandbox Code Playgroud)
这个程序在我的平台上包含未定义的行为,因为它a会在第3个循环中溢出.
这是否会使整个程序具有未定义的行为,或者仅在溢出实际发生之后?编译器是否可能a 会解决溢出问题,因此它可以声明整个循环未定义,并且不会打扰运行printfs,即使它们都在溢出之前发生?
(标记为C和C++,即使它们不同,因为如果它们不同,我会对这两种语言的答案感兴趣.)
受此问题的启发,关于编译器是否可以优化掉对函数的调用而没有副作用.假设我有以下代码:
delete[] new char[10];
Run Code Online (Sandbox Code Playgroud)
它没有任何用处.但它有副作用吗?堆分配后立即释放被认为是副作用吗?
(注意:通过"常数时间",我的意思是当其中一个输入固定而不是O(1)时,机器周期数恒定.这是加密环境中术语的标准含义.)
将固定值与相同大小的未知值进行比较的最常见方法是,通过定时泄漏没有关于固定值的信息是使用XOR循环:
bool compare(const char* fixed, const char* unknown, size_t n)
{
char c = 0;
for (size_t i=0; i<n; ++i)
c |= fixed[i] ^ unknown[i];
return (c == 0);
}
Run Code Online (Sandbox Code Playgroud)
GCC 4.6.3和CLANG 3.0不会在AMD64上短路此循环,即使在-O3(我检查了生成的机器代码).但是,我不知道C标准中的任何内容会阻止某些聪明的编译器识别出如果c非零,那么该函数只能返回false.
如果你愿意接受大的性能损失和概率比较而不是确定性的比较,那么实现恒定时间比较的更偏执方式是计算两个输入的加密哈希值并比较哈希值; 如何对哈希进行比较并不重要,因为除非攻击者能够计算哈希的前映像,否则它不能进行未知值的连续近似.很难想象编译器有足够的智能来优化它,但我无法保证它不会发生.更偏执的方法是使用HMAC和特定于实例的键而不是简单的哈希,尽管我仍然可以想象一个奇迹般智能的优化器,它可以识别出无论使用什么键,它编译的算法只是一种缓慢的方式.进行字符串比较并相应地优化.通过添加额外的复杂层,调用共享库等,我可以使我的比较任意难以优化,但我仍然不能保证没有符合标准的编译器可以使我的比较短路并让我容易受到攻击定时攻击.
有没有办法实现有效的,确定性的,恒定时间的比较,保证始终按照C标准工作?如果没有,任何流行的C(或C++)编译器或库是否提供这样的方法?请引用你的消息来源.
下面声明norm的C++ vector类中的成员函数标记为const和(据我所知)不包含任何副作用.
template <unsigned int N>
struct vector {
double v[N];
double norm() const {
double ret = 0;
for (int i=0; i<N; ++i) {
ret += v[i]*v[i];
}
return ret;
}
};
double test(const vector<100>& x) {
return x.norm() + x.norm();
}
Run Code Online (Sandbox Code Playgroud)
如果我打电话norm多次上const的实例vector(见test上面的函数)与GCC编译器(5.4版本)和优化开启(即-O3),那么编译器内联norm,但仍计算的结果norm多次,即使结果不应该改变.为什么编译器不优化第二次调用norm而只计算一次这个结果?这个答案似乎表明,如果编译器确定该norm函数没有任何副作用,编译器应该执行此优化.为什么在这种情况下不会发生这种情况?
请注意,我正在使用Compiler Explorer确定编译器生成的内容,并且下面给出了gcc版本5.4的程序集输出.clang编译器给出了类似的结果.另请注意,如果我使用gcc的编译器属性手动标记norm为使用const函数__attribute__((const)),那么第二次调用会根据需要进行优化,但我的问题是为什么gcc(和clang)不会自动执行此操作,因为norm定义可用?
test(vector<100u>&): …Run Code Online (Sandbox Code Playgroud) 我多次被告知volatile无法优化对对象的访问,但在我看来,C89,C99和C11标准中的这一部分建议不然:
...实际实现不需要评估表达式的一部分,如果它可以推断出它的值未被使用并且不产生所需的副作用(包括由调用函数或访问易失性对象引起的任何副作用).
如果我理解正确,这句话就说明实际的实现可以优化表达式的一部分,只要满足这两个要求:
在我看来,很多人都把"包括"的含义与"排除"的含义混为一谈.
编译器是否可以区分"需要"的副作用和不需要的副作用?如果时间被认为是一个必要的副作用,那么为什么允许编译器优化掉像/ do_nothing();或类似的空操作int unused_variable = 0;?
如果编译器能够推断函数什么都不做(例如void do_nothing() { }),那么编译器是否可能有理由优化对该函数的调用?
如果编译器能够推断出一个volatile对象没有被映射到任何关键的东西(也许它被映射/dev/null到形成一个空操作),那么编译器是否也可能有理由来优化那个非关键的副作用远?
如果编译器可以执行优化来消除不必要的代码,例如do_nothing()在称为"死代码消除"的过程中调用(这是很常见的做法),那么为什么编译器也不能消除对空设备的易失性写入?
据我所知,编译器可以优化对函数或易失性访问的调用,或者由于5.1.2.3p4,编译器也无法优化掉.
我正在使用C++ 98.函数调用在多大程度上可以重新排序?我没有使用任何全局状态,只使用函数本地的对象状态.
我的具体情况是:
{
RaiiType T;
Object1.FunctionCall();
Object2.FunctionCall();
}
Run Code Online (Sandbox Code Playgroud)
Object1和Object2在下一个范围内声明的位置.是否T允许在函数调用之后重新排序构造函数,假设它可以被简单地证明(至少对于人类)构造和函数调用之间没有依赖关系?
在我的特定情况下,RAII对象用于计算函数调用的执行时间.
假设我有如下伪 C 代码:
int x = 0;
int y = 0;
int __attribute__ ((noinline)) func1(void)
{
int prev = x; (1)
x |= FLAG; (2)
return prev; (3)
}
int main(void)
{
int tmp;
...
y = 5; (4)
compiler_mem_barrier();
func1();
compiler_mem_barrier();
tmp = y; (5)
...
}
Run Code Online (Sandbox Code Playgroud)
假设这是一个单线程进程,所以我们不需要担心锁。假设代码在 x86 系统上运行。我们还假设编译器不进行任何重新排序。
据我了解,x86 系统只能重新排序写入/读取指令(读取可能会与较旧的写入重新排序到不同的位置,但不能与较旧的写入重新排序到同一位置)。但我不清楚 call/ret 指令是否被视为 WRITE/READ 指令。这是我的问题:
在 x86 系统上,“call”是否被视为 WRITE 指令?我认为是这样,因为调用会将地址推入堆栈。但我没有找到官方文件正式这么说。所以请大家帮忙确认一下。
出于同样的原因,“ret”是否被视为 READ 指令(因为它从堆栈中弹出地址)?
实际上,“ret”指令可以在函数内重新排序吗?例如,下面的ASM代码中(3)可以在(2)之前执行吗?这对我来说没有意义,但“ret”不是序列化指令。我在英特尔手册中没有找到任何地方说“ret”不能重新排序。
上面的代码中,(1)可以先于(4)执行吗?据推测,读指令 (1) 可以在写指令 (4) 之前重新排序。“call”指令可能有“jmp”部分,但是具有推测执行......所以我觉得它可能会发生,但我希望更熟悉这个问题的人可以证实这一点。
上面的代码中,(5)可以先于(2)执行吗?如果“ret”被认为是一个READ指令,那么我认为它不会发生。但我再次希望有人能证实这一点。
如果需要 func1() 的汇编代码,它应该类似于:
mov %gs:0x24,%eax (1)
orl $0x8,%gs:0x24 …Run Code Online (Sandbox Code Playgroud) 我有一个类,它使用互斥锁和两个用于同步的条件变量来实现线程化的生产者/消费者系统。当有项目要使用时,生产者向消费者线程发送信号,消费者在消费完项目时向生产者线程发送信号。线程继续生产和消费,直到析构函数通过设置布尔变量请求它们退出。因为其中一个线程可能正在等待一个条件变量,所以我不得不对quit变量进行第二次检查,感觉不对,很乱……
我已将问题简化为以下示例(使用 g++4.7 在 GNU/Linux 上工作)示例:
// C++11and Boost required.
#include <cstdlib> // std::rand()
#include <cassert>
#include <boost/circular_buffer.hpp>
#include <atomic>
#include <chrono>
#include <condition_variable>
#include <iostream>
#include <mutex>
#include <thread>
#include <vector>
// Creates a single producer and single consumer thread.
class prosumer
{
public:
// Create the circular buffer and start the producer and consumer thread.
prosumer()
: quit_{ false }
, buffer_{ circular_buffer_capacity }
, producer_{ &prosumer::producer_func, this }
, consumer_{ &prosumer::consumer_func, this }
{}
// Set the …Run Code Online (Sandbox Code Playgroud)