Modifica 2 :
Stavo eseguendo il debug di uno strano errore di test quando una funzione precedentemente residente in un file sorgente C ++ ma spostata in un file C alla lettera, iniziava a restituire risultati errati. Il MVE qui sotto consente di riprodurre il problema con GCC. Tuttavia, quando, per un capriccio, ho compilato l'esempio con Clang (e più tardi con VS), ho ottenuto un risultato diverso! Non riesco a capire se trattare questo come un bug in uno dei compilatori o come manifestazione di risultati indefiniti consentiti dallo standard C o C ++. Stranamente, nessuno dei compilatori mi ha dato alcun avvertimento sull'espressione.
Il colpevole è questa espressione:
ctl.b.p52 << 12;
Qui, p52
viene digitato come uint64_t
; fa anche parte di un'unione (vedi control_t
sotto). L'operazione di spostamento non perde alcun dato poiché il risultato si adatta ancora a 64 bit. Tuttavia, GCC decide di troncare il risultato a 52 bit se uso il compilatore C ! Con il compilatore C ++, vengono conservati tutti i 64 bit del risultato.
Per illustrare ciò, il seguente programma di esempio compila due funzioni con corpi identici, quindi confronta i loro risultati. c_behavior()
viene inserito in un file sorgente C e cpp_behavior()
in un file C ++ e main()
fa il confronto.
Repository con il codice di esempio: https://github.com/grigory-rechistov/c-cpp-bitfields
L'intestazione common.h definisce un'unione di campi di bit e intero a 64 bit e dichiara due funzioni:
#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
Le funzioni hanno corpi identici, tranne per il fatto che uno è trattato come C e un altro come 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;
}
GCC mostra la differenza tra i risultati che restituiscono:
$ gcc -Wpedantic main.c c-part.c cpp-part.cpp
$ ./a.exe
OMG C != C++
Tuttavia, con Clang C e C ++ si comportano in modo identico e come previsto:
$ clang -Wpedantic main.c c-part.c cpp-part.cpp
$ ./a.exe
C == C++
Con Visual Studio ottengo lo stesso risultato di 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++
Ho provato gli esempi su Windows, anche se il problema originale con GCC è stato scoperto su Linux.
main.c
e probabilmente causa comportamenti indefiniti in diversi modi. IMO sarebbe più chiaro pubblicare un MRE a file singolo che produce output diversi quando compilato con ciascun compilatore. Perché l'interoperabilità C-C ++ non è ben specificata dallo standard. Si noti inoltre che l'aliasing unione provoca UB in C ++.