компилятор игнорирует оператор новое распределение - PullRequest
0 голосов
/ 09 апреля 2019

Я программирую 512-битное целое число в C ++. Для целого числа я выделяю память из кучи, используя ключевое слово new, но компилятор (g ++ версии 8.1 на MINGW), похоже, неправильно оптимизирует это. То есть команды компилятора:

g++ -Wall -fexceptions -Og -g -fopenmp -std=c++14 -c main.cpp -o main.o

g++ -o bin\Debug\cs.exe obj\Debug\main.o -O0 -lgomp

Код:

#include <iostream>
#include <cstdint>
#include <omp.h>

constexpr unsigned char arr_size = 16;
constexpr unsigned char arr_size_half = 8;
void exit(int);

struct uint512_t{
    uint32_t * bytes;
    uint512_t(uint32_t num){
        //The line below is either (wrongfully) ignored or (wrongfully) optimized out
        bytes = new(std::nothrow) uint32_t[arr_size];
        if(!bytes){
            std::cerr << "Error - not enough memory available.";
            exit(-1);
        }
        *bytes = num;
        for(uint32_t * ptr = bytes+1; ptr < ptr+16; ++ptr){
            //OS throws error 0xC0000005 (accessing unallocated memory) here
            *ptr = 0;
        }
    }
    uint512_t inline operator &(uint512_t &b){
        uint32_t* itera = bytes;
        uint32_t* iterb = b.bytes;
        uint512_t ret(0);
        uint32_t* iterret = ret.bytes;
        for(char i = 0; i < arr_size; ++i){
            *(iterret++) = *(itera++) & *(iterb++);
        }
        return ret;
    }

    uint512_t inline operator =(uint512_t &b){
        uint32_t * itera=bytes, *iterb=b.bytes;
        for(char i = 0; i < arr_size; ++i){
            *(itera++) = *(iterb++);
        }
        return *this;
    }
    uint512_t inline operator + (uint512_t &b){
        uint32_t * itera = bytes;
        uint32_t * iterb = b.bytes;
        uint64_t res = 0;
        uint512_t ret(0);
        uint32_t *p2ret = ret.bytes;
        uint32_t *p2res = 1+(uint32_t*)&res;
        //#pragma omp parallel for shared(p2ret, res, p2res, itera, iterb, ret) private(i, arr_size) schedule(auto)
        for(char i = 0; i < arr_size;++i){
            res = *p2res;
            res += *(itera++);
            res += *(iterb++);
            *(p2ret++) = (i<15) ? res+*(p2res) : res;
        }
        return ret;
    }
    uint512_t inline operator += (uint512_t &b){
        uint32_t * itera = bytes;
        uint32_t * iterb = b.bytes;
        uint64_t res = 0;
        uint512_t ret(0);
        uint32_t *p2ret = ret.bytes;
        uint32_t *p2res = 1+(uint32_t*)&res;
        //#pragma omp parallel for shared(p2ret, res, p2res, itera, iterb, ret) private(i, arr_size) schedule(auto)
        for(char i = 0; i < arr_size;++i){
            res = *p2res;
            res += *(itera++);
            res += *(iterb++);
            *(p2ret++) = (i<15) ? res+(*p2res) : res;
        }
        (*this) = ret;
        return *this;
    }
    //uint512_t inline operator * (uint512_t &b){
    //}
    ~uint512_t(){
        delete[] bytes;
    }
};

int main(void){
    uint512_t a(3);
}

Ответы [ 4 ]

5 голосов
/ 09 апреля 2019

ptr < ptr+16 всегда верно.Цикл бесконечен и в конечном итоге переполняет буфер, в который он записывает.

Простое решение: значение инициализирует массив так, что вам не нужен цикл:

bytes = new(std::nothrow) uint32_t[arr_size]();
//                                          ^^

PS.Если вы копируете экземпляр, поведение будет неопределенным, поскольку копия будет указывать на одно и то же распределение, и оба экземпляра будут пытаться удалить его в деструкторе.

Простое решение: не используйте голые указатели-владельцы.Используйте контейнер RAII, например std::vector, если вам нужно динамически выделить массив.


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

5 голосов
/ 09 апреля 2019

Ошибка в этой строке и не имеет никакого отношения к оптимизации new:

for(uint32_t * ptr = bytes+1; ptr < ptr+16; ++ptr){
    *ptr = 0;
}

Условие для for неверное.ptr < ptr+16 никогда не будет ложным. Цикл будет продолжаться вечно, и в конечном итоге вы будете разыменовывать неверную ячейку памяти, потому что ptr получает увеличенное бесконечное число.


ККстати, компилятору разрешено выполнять оптимизацию, но ему не разрешено изменять кажущееся поведение программы.Если ваш код выполняет new, компилятор может оптимизировать его, если он может обеспечить наличие побочных эффектов new, когда они вам нужны (в данном случае в тот момент, когда вы обращаетесь к массиву).

0 голосов
/ 09 апреля 2019

ps Я попробовал ваше решение, и оно работало нормально -

bytes = new(std::nothrow) uint32_t[arr_size];
    if(!bytes){
        std::cerr << "Error - not enough memory available.";
        exit(-1);
    }
    *bytes = num;
    auto ptrp16 = bytes+16;
    for(uint32_t * ptr = bytes+1;ptr < ptrp16 ; ++ptr){
        *ptr = 0;
    }
0 голосов
/ 09 апреля 2019

Вы обращаетесь к массиву за пределами.Наименьшим воспроизводимым примером будет:

#include <cstdint>
int main() {
        uint32_t bytes[16];
        for(uint32_t * ptr = bytes + 1; ptr < ptr + 16; ++ptr){
            //OS throws error 0xC0000005 (accessing unallocated memory) here
            *ptr = 0;
        }
}

Значение ptr < ptr + 16 всегда истинно (возможно, за исключением переполнения).

...