Tenho uma base de código C que usa uniões para trocadilhos de tipos. Estou então testando esse código em C++ com o gmock.
Sei que o trocadilho baseado em união é ilegal em C++ , então queria ter certeza de que meu uso é seguro:
// sum_halves.h
#include <stdint.h>
uint32_t sum_halves(unit64_t i);
// sum_halves.c
#include "sum_halves.h"
uint32_t sum_halves(unit64_t i) {
union {
uint64_t u64;
uint32_t u32[2];
} u;
u.u64 = i;
return u.u32[0] + u.u32[1];
}
// sum_halves_test.cpp
extern "C" {
#include "sum_halves.h"
}
#include <gtest/gtest.h>
TEST(sum_halves_test, two_plus_two_is_five) {
EXPECT_EQ(sum_halves(0x0000'0002'0000'0003), 5);
}
Pelo que entendi, como sum_halves.c
é compilado como um programa em C, o trocadilho baseado em união está correto, mesmo que eu posteriormente chame essa função a partir de código C++. Mas se eu criasse sum_halves
uma função inline, ela seria compilada como código C++ em *.cpp
arquivos, resultando em Comportamento Indefinido. extern "C"
Isso não ajuda, pois afeta apenas a vinculação, não a compilação.
Se estiver correto, existe alguma solução alternativa para o caso inline? Uma flag do compilador GCC ou Clang ou #pragma
para permitir trocadilhos baseados em união em uma função específica?
É fácil escrever código seguro e compatível com os padrões que funcione em ambas as linguagens:
Se o seu código real for mais complexo, você também pode usar
memcpy()
para copiar alguns bytes de um inteiro para outro com segurança. O compilador otimizará isso em código de máquina eficiente, sem nenhuma chamada de função real para copiar alguns bytes.Usar C e C++ no mesmo programa é um comportamento indefinido tanto para o padrão C quanto para o padrão C++.
O comportamento de
sum_halves
por si só é definido pelo padrão C.O comportamento de
sum_halves_test
por si só é definido pelo padrão C++.Se as duas rotinas forem compiladas separadamente em suas respectivas linguagens, vinculadas sem nenhuma interação entre módulos além de obedecer à ABI (Application Binary Interface) da plataforma, essa vinculação, somada ao fato de que cada rotina deve estar em conformidade com as especificações de sua linguagem, significa que elas funcionarão juntas.
Você está absolutamente certo. O trocadilho baseado em união é bem definido em C (permitido na prática), mas o comportamento é indefinido em C++, mesmo com extern "C", porque extern "C" controla a ligação, não a semântica da linguagem.
O modelo de memória do C++ é mais rigoroso. Ele não permite o acesso a membros inativos de uma união, a menos que você esteja em um ambiente std::variant ou execute um memcpy. C segue um modelo de aliasing mais pragmático, especialmente para operações embarcadas e de nível de bits.
Se você precisar de uma versão inline ou de cabeçalho compartilhado, em vez de trocadilhos de união, como:
Esta opção é um wrapper seguro baseado em macro para várias linguagens e amigável ao compilador.