gcc оптимизирует составной оператор

У меня проблема с реализацией оператора push_back для универсального вектора с изменяемым размером в c. Для универсальности мне нужно использовать указатель void в качестве аргумента, но на практике я хочу напрямую передавать ему значения.

Когда я компилирую приведенный ниже код с gcc -o t test.c -std=c99, он печатает 10, как я и ожидал. Когда я добавляю -O1 (или выше) к параметрам компиляции, программа печатает 0.

Я думаю, что проблема в коде smemcpy, так как когда я заменяю его на memcpy, у меня больше нет этой проблемы.

Упрощенный код:

#include <stdio.h>
#include <stdlib.h>

#define get_pointer(value) ({ __typeof__(value) tmp = value; &tmp; })

// copy from src to dst byte by byte
void* smemcpy(void* dst, void const * src, size_t len) {
    char * pdst = (char *) dst;
    char const * psrc = (char const *) src;

    while (len--) {
        *pdst++ = *psrc++;
    }

    return (dst);
}


int main() {
    void* container = malloc(sizeof(int));

    // copy a 10 into the container via a temporary pointer
    smemcpy(container, get_pointer(10), sizeof(int));

    printf("%d\n", ((int*)container)[0]);

    return 0;
}

Заранее благодарю за любую помощь,

B


person user1483596    schedule 25.12.2013    source источник
comment
Ваш блок операторов get_pointer вычисляется по адресу временного объекта, не так ли?   -  person    schedule 26.12.2013
comment
Второй вопрос такого типа сегодня, третий за два дня.   -  person    schedule 26.12.2013
comment
@hwd Если бы это была проблема с временным объектом, почему она работала бы без -O1 или когда я использую memcpy?   -  person user1483596    schedule 26.12.2013
comment
@ user1483596: Неопределенное поведение.   -  person Oliver Charlesworth    schedule 26.12.2013
comment
Я посмотрел ассемблерный код. gcc с -O1 оптимизирует назначение tmp = value, но по-прежнему передает &tmp в smemcpy.   -  person Mark Plotnick    schedule 26.12.2013


Ответы (2)


В определении get_pointer используется оператор в выражении, которое представляет собой расширение GCC. Семантика этого едва документирована, и нет причин полагать, что продолжительность хранения объекта, объявленного в выражении-операторе, сохраняется после оценки оператора.

Таким образом, при подготовке вызова smemcpy компилятор может оценить get_pointer, создав объект tmp, выдав его адрес в качестве значения оператора-выражения и уничтожив объект tmp. Затем уже недействительный адрес несуществующего объекта передается в smemcpy, который копирует недопустимые данные, поскольку пространство, используемое для tmp, было повторно использовано для другой цели.

Код может работать, когда используется memcpy, потому что memcpy — это специальная функция, известная GCC, и GCC оптимизирует ее различными способами.

Составной литерал должен работать; стандарт C указывает, что составной литерал в теле функции имеет автоматическую продолжительность хранения, связанную с окружающим блоком. Если мы определим get_pointer следующим образом, объемлющий блок будет включать весь вызов smemcpy:

#define get_pointer(value) (& (__typeof__(value)) { value })
person Eric Postpischil    schedule 26.12.2013

возможно проблема в макросе. переменная tmp определена в блоке, я предполагаю, что блок остается сразу после его оценки, то есть до ввода smemcpy. таким образом адрес переменной становится бессмысленным. адрес может быть перезаписан какой-либо оптимизацией, повторно использующей память, или может остаться нетронутым в неоптимизированной сборке. просто идея, не могу подтвердить.

person vecvecvec    schedule 26.12.2013