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_tabaixo). 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.ce 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 ++.