Буферы разделяемой памяти в C ++ без нарушения строгих правил наложения имен

Я борюсь с реализацией буфера совместно используемой памяти без нарушения строгих правил алиасинга в C99.

Предположим, у меня есть некоторый код, который обрабатывает некоторые данные и должен иметь «чистую» память для работы. Я мог бы написать это как-то так:

void foo(... some arguments here ...) {
int* scratchMem = new int[1000];   // Allocate.
// Do stuff...
delete[] scratchMem;  // Free.
}

Затем у меня есть другая функция, которая делает некоторые другие вещи, которые также нуждаются в буфере очистки:

void bar(...arguments...) {
float* scratchMem = new float[1000];   // Allocate.
// Do other stuff...
delete[] scratchMem;  // Free.
}

Проблема заключается в том, что foo () и bar () могут вызываться много раз во время работы, и распределение ресурсов кучи повсеместно может быть довольно плохим с точки зрения производительности и фрагментации памяти. Очевидным решением было бы выделить общий буфер общей памяти надлежащего размера один раз, а затем передать его в foo () и bar () в качестве аргумента в стиле BYOB:

void foo(void* scratchMem);
void bar(void* scratchMem);

int main() {
const int iAmBigEnough = 5000;
int* scratchMem = new int[iAmBigEnough];

foo(scratchMem);
bar(scratchMem);

delete[] scratchMem;
return 0;
}

void foo(void* scratchMem) {
int* smem = (int*)scratchMem;
// Dereferencing smem will break strict-aliasing rules!
// ...
}

void bar(void* scratchMem) {
float* smem = (float*)scratchMem;
// Dereferencing smem will break strict-aliasing rules!
// ...
}

Я думаю, у меня есть два вопроса сейчас:
— Как я могу реализовать общий буфер с общей буферной памятью, который не нарушает правила псевдонимов?
— Несмотря на то, что приведенный выше код нарушает строгие правила псевдонимов, «псевдоним» не наносит «вреда». Поэтому может ли какой-нибудь здравомыслящий компилятор генерировать (оптимизированный) код, который все еще доставляет мне неприятности?

Спасибо

5

Решение

На самом деле, то, что вы написали, не является строгим нарушением псевдонимов.

C ++ 11 spec 3.10.10 говорит:

Если программа пытается получить доступ к сохраненному значению объекта через glvalue, отличный от одного из
следующие типы поведение не определено

Таким образом, вещь, которая вызывает неопределенное поведение, это доступ к сохраненному значению, а не просто создание указателя на него. Ваш пример ничего не нарушает. Это должно было бы сделать следующий шаг: float badValue = smem [0]. Smem [0] получает сохраненное значение из общего буфера, создавая нарушение псевдонимов.

Конечно, вы не собираетесь просто захватывать smem [0] перед установкой. Вы собираетесь написать в первую очередь. Присвоение одной и той же памяти не дает доступа к сохраненному значению, поэтому нет необходимости в использовании. Однако, нельзя писать поверх объекта, пока он еще жив. Чтобы доказать, что мы в безопасности, нам нужна продолжительность жизни объекта из 3.8.4:

Программа может закончить время жизни любого объекта, повторно используя хранилище, которое занимает объект, или
явный вызов деструктора для объекта типа класса с нетривиальным деструктором. Для объекта
типа класса с нетривиальным деструктором, программе не требуется явно вызывать деструктор
до повторного использования или освобождения хранилища, которое занимает объект; … [продолжает о последствиях отказа от вызова деструкторов]

У вас есть POD-тип, такой тривиальный деструктор, так что вы можете просто объявить в устной форме «все объекты int находятся в конце своей жизни, я использую пространство для float». Затем вы повторно используете пространство для поплавков, и нарушения псевдонимов не происходит.

2

Другие решения

Всегда допустимо интерпретировать объект как последовательность байтов (т.е. не нарушение псевдонимов для обработки любого указателя объекта как указателя на первый элемент массива символов), и вы можете создать объект в любой части памяти, которая достаточно велика и выровнена соответствующим образом.

Таким образом, вы можете выделить большой массив chars (любая подпись) и найдите смещение, которое установлено на alignof(maxalign_t); теперь вы можете интерпретировать этот указатель как указатель на объект после того, как вы сконструировали соответствующий объект (например, с помощью размещения-нового в C ++).

Вы, конечно, должны убедиться, что не записываете в память существующего объекта; на самом деле время жизни объекта тесно связано с тем, что происходит с памятью, которая представляет объект.

Пример:

char buf[50000];

int main()
{
uintptr_t n = reinterpret_cast<uintptr_t>(buf);
uintptr_t e = reinterpret_cast<uintptr_t>(buf + sizeof buf);

while (n % alignof(maxalign_t) != 0) { ++n; }

assert(e > n + sizeof(T));

T * p = :: new (reinterpret_cast<void*>(n)) T(1, false, 'x');

// ...

p->~T();
}

Обратите внимание, что память, полученная malloc или же new char[N] является всегда выровнен для максимального выравнивания (но не больше, и вы можете использовать переопределенные адреса).

1

Если для хранения переменных int и float используется объединение, то вы можете пропустить строгий псевдоним. Подробнее об этом дается в
http://cellperformance.beyond3d.com/articles/2006/06/understanding-strict-aliasing.html

Также см. Следующую статью.

http://blog.regehr.org/archives/959

Он дает способ использовать союзы, чтобы сделать это.

0
По вопросам рекламы [email protected]