Edición 2 :
Estaba depurando un extraño error de prueba cuando una función que anteriormente residía en un archivo fuente C ++ pero se movió literalmente a un archivo C, comenzó a devolver resultados incorrectos. El MVE a continuación permite reproducir el problema con GCC. Sin embargo, cuando, por capricho, compilé el ejemplo con Clang (y luego con VS), ¡obtuve un resultado diferente! No puedo determinar si tratar esto como un error en uno de los compiladores, o como una manifestación de resultado indefinido permitido por el estándar C o C ++. Curiosamente, ninguno de los compiladores me dio ninguna advertencia sobre la expresión.
El culpable es esta expresión:
ctl.b.p52 << 12;
Aquí, p52
se escribe como uint64_t
; También es parte de una unión (ver control_t
abajo). La operación de cambio no pierde ningún dato ya que el resultado todavía cabe en 64 bits. Sin embargo, ¡entonces GCC decide truncar el resultado a 52 bits si uso el compilador de C ! Con el compilador de C ++, se conservan los 64 bits de resultado.
Para ilustrar esto, el siguiente programa de ejemplo compila dos funciones con cuerpos idénticos y luego compara sus resultados. c_behavior()
se coloca en un archivo fuente C y cpp_behavior()
en un archivo C ++, y main()
hace la comparación.
Repositorio con el código de ejemplo: https://github.com/grigory-rechistov/c-cpp-bitfields
El encabezado common.h define una unión de campos de bits anchos de 64 bits y enteros y declara dos funciones:
#ifndef COMMON_H
#define COMMON_H
#include <stdint.h>
typedef union control {
uint64_t q;
struct {
uint64_t a: 1;
uint64_t b: 1;
uint64_t c: 1;
uint64_t d: 1;
uint64_t e: 1;
uint64_t f: 1;
uint64_t g: 4;
uint64_t h: 1;
uint64_t i: 1;
uint64_t p52: 52;
} b;
} control_t;
#ifdef __cplusplus
extern "C" {
#endif
uint64_t cpp_behavior(control_t ctl);
uint64_t c_behavior(control_t ctl);
#ifdef __cplusplus
}
#endif
#endif // COMMON_H
Las funciones tienen cuerpos idénticos, excepto que uno se trata como C y otro como C ++.
c-part.c:
#include <stdint.h>
#include "common.h"
uint64_t c_behavior(control_t ctl) {
return ctl.b.p52 << 12;
}
cpp-part.cpp:
#include <stdint.h>
#include "common.h"
uint64_t cpp_behavior(control_t ctl) {
return ctl.b.p52 << 12;
}
C Principal:
#include <stdio.h>
#include "common.h"
int main() {
control_t ctl;
ctl.q = 0xfffffffd80236000ull;
uint64_t c_res = c_behavior(ctl);
uint64_t cpp_res = cpp_behavior(ctl);
const char *announce = c_res == cpp_res? "C == C++" : "OMG C != C++";
printf("%s\n", announce);
return c_res == cpp_res? 0: 1;
}
GCC muestra la diferencia entre los resultados que devuelven:
$ gcc -Wpedantic main.c c-part.c cpp-part.cpp
$ ./a.exe
OMG C != C++
Sin embargo, con Clang C y C ++ se comportan de manera idéntica y como se esperaba:
$ clang -Wpedantic main.c c-part.c cpp-part.cpp
$ ./a.exe
C == C++
Con Visual Studio obtengo el mismo resultado que con Clang:
C:\Users\user\Documents>cl main.c c-part.c cpp-part.cpp
Microsoft (R) C/C++ Optimizing Compiler Version 19.00.24234.1 for x64
Copyright (C) Microsoft Corporation. All rights reserved.
main.c
c-part.c
Generating Code...
Compiling...
cpp-part.cpp
Generating Code...
Microsoft (R) Incremental Linker Version 14.00.24234.1
Copyright (C) Microsoft Corporation. All rights reserved.
/out:main.exe
main.obj
c-part.obj
cpp-part.obj
C:\Users\user\Documents>main.exe
C == C++
Probé los ejemplos en Windows, aunque el problema original con GCC se descubrió en Linux.
<<
operador que requiere el truncamiento.
main.c
y probablemente causa un comportamiento indefinido de varias maneras. En mi opinión, sería más claro publicar una MRE de un solo archivo que produce una salida diferente cuando se compila con cada compilador. Porque la interoperabilidad C-C ++ no está bien especificada por el estándar. También tenga en cuenta que el alias de unión causa UB en C ++.