Edição 2 :
Eu estava depurando uma falha de teste estranha quando uma função que residia anteriormente em um arquivo de origem C ++, mas foi movida para um arquivo C literalmente, começou a retornar resultados incorretos. O MVE abaixo permite reproduzir o problema com o GCC. No entanto, quando eu, por um capricho, compilei o exemplo com Clang (e mais tarde com o VS), obtive um resultado diferente! Não consigo descobrir se devo tratar isso como um bug em um dos compiladores ou como manifestação de resultado indefinido permitido pelo padrão C ou C ++. Estranhamente, nenhum dos compiladores me deu nenhum aviso sobre a expressão.
O culpado é esta expressão:
ctl.b.p52 << 12;
Aqui, p52
é digitado como uint64_t
; também faz parte de um sindicato (veja control_t
abaixo). A operação de deslocamento não perde dados, pois o resultado ainda se encaixa em 64 bits. No entanto, o GCC decide truncar o resultado para 52 bits se eu usar o compilador C ! Com o compilador C ++, todos os 64 bits de resultado são preservados.
Para ilustrar isso, o programa de exemplo abaixo compila duas funções com corpos idênticos e depois compara seus resultados. c_behavior()
é colocado em um arquivo de origem C e cpp_behavior()
em um arquivo C ++ e main()
faz a comparação.
Repositório com o código de exemplo: https://github.com/grigory-rechistov/c-cpp-bitfields
O cabeçalho common.h define uma união de campos de bits e números inteiros de 64 bits e declara duas funções:
#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
As funções têm corpos idênticos, exceto que um é tratado como C e outro 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;
}
main.c:
#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;
}
O GCC mostra a diferença entre os resultados que eles retornam:
$ gcc -Wpedantic main.c c-part.c cpp-part.cpp
$ ./a.exe
OMG C != C++
No entanto, com Clang C e C ++ se comportam de forma idêntica e conforme o esperado:
$ clang -Wpedantic main.c c-part.c cpp-part.cpp
$ ./a.exe
C == C++
Com o Visual Studio, obtenho o mesmo resultado que com o 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++
Eu tentei os exemplos no Windows, mesmo que o problema original com o GCC tenha sido descoberto no Linux.
main.c
e provavelmente causa comportamento indefinido de várias maneiras. Na IMO, seria mais claro publicar um MRE de arquivo único que produza saída diferente quando compilado com cada compilador. Porque a interoperabilidade C-C ++ não é bem especificada pelo padrão. Observe também que o alias de união causa UB em C ++.