O uso de reinterpret_cast em um buffer de memória UB?

8

Dado o código

struct A {};

auto obj = new A;
std::vector<unsigned char> buffer;
buffer.resize(sizeof(obj));
std::memcpy(buffer.data(), &obj, sizeof(obj));  // this copies the pointer, not the object!

// ...

auto ptr = *reinterpret_cast<A**>(buffer.data()); // is this UB?
delete ptr;

é o uso de reinterpret_cast, neste caso, UB? Eu diria que sim, porque memcpynão inicia o tempo de vida de uma instância, violando a regra estrita de alias (por isso std::bit_castfoi adicionada ao C ++ 20).

E se eu substituir o elenco por outro memcpy(para ler o ponteiro) o programa estaria bem definido?

Timo
fonte
linguagem advogado à parte, é simplesmente errado. É o conteúdo apontado por buffer.data()que supostamente contém um ponteiro para A, e não buffer.data()ele mesmo que é um ponteiro para A.
StoryTeller - Unslander Monica
3
Existem garantias de alinhamento da memória alocada do armazenamento de backup por um std::vector? (Presumo suas garantias são quaisquer que sejam suas garantias alocador.)
Eljay
1
Eu acho que também quebra alias estrito.
Algum programador
1
@anastaciu Primeiro hit no Google - stats.meta.stackexchange.com/q/5783/3512
Konrad Rudolph
1
OK, depois de reler a pergunta, ela é realmente UB porque (1) os requisitos de alinhamento estão potencialmente quebrados e (2) aqui não há A*objeto no buffer. O padrão diz sobre a função alocador padrão :: alocar: "Retorna: um ponteiro para o elemento inicial de uma matriz de armazenamento de tamanho n * sizeof(T), alinhado adequadamente para objetos do tipo T ".
n. 'pronomes' m.

Respostas:

9

Sim, este código tem um comportamento indefinido. Não há nenhum objeto do tipo A*no local apontado por buffer.data(). Tudo o que você fez foi copiar a representação do objeto desse ponteiro no seu vetor [basic.types] / 4 . Como os ponteiros são trivialmente copiáveis [basic.types] / 9 , se você copiar novamente esses bytes em um objeto real do tipo A*e, em seguida, deleteo valor disso, isso seria bem definido [basic.types] / 3 . Então, é isso

A* ptr;
std::memcpy(&ptr, buffer.data(), sizeof(ptr));
delete ptr;

seria ótimo.

Observe que não é o próprio elenco que chama um comportamento indefinido no seu exemplo original, mas sua tentativa subseqüente de ler o valor de um objeto do tipo A*que não existe onde o ponteiro obtido pelos pontos de elenco. Tudo o que existe onde o ponteiro aponta é uma sequência de objetos do tipo unsigned char. O tipo A*não é um tipo que você pode usar para acessar o valor armazenado de um objeto do tipo unsigned char [basic.lval] / 8

Michael Kenzel
fonte
2
Acredito que esta resposta esteja correta, mas a última frase é um pouco surpreendente, uma vez que existe uma boa quantidade de código existente fazendo essencialmente o que a pergunta faz (praticamente todas as implementações de contêineres personalizadas, para começar), e atualmente não há realmente uma boa maneira de contornar isso em alguns casos.
Konrad Rudolph
1
A* a_ptr; std::memcpy(&a_ptr, buffer.data(), sizeof(a_ptr));para extrair o ponteiro do buffer. Em vez reinterpret_castda pergunta de Timo.
Eljay 13/01
Advogado de idiomas à parte, por que isso é realmente UB? Aliasing estrito vem à mente, há algo mais?
divinas 13/01
@MichaelKenzel, há um objeto do tipo unsigned charlá, talvez mais de um.
n. 'pronomes' m.
1
@MichaelKenzel É claro que não é defensável, pois é UB. Mas iniciar a vida útil do objeto é realmente difícil no C ++ (atual) e toneladas de código (caso contrário, de alta qualidade!) Falham em fazê-lo corretamente, consulte p0593r2 §2.3 em particular, mas também §2.2. E como você menciona a compactação: na verdade é o que minha empresa faz, e vamos apenas dizer que nossa base de código não presta atenção a esses problemas nem um pouco (admitidamente, uma vez que grande parte dela é derivada do C idiomático).
Konrad Rudolph