Modifier 2 :
Je déboguais un échec de test étrange lorsqu'une fonction résidant précédemment dans un fichier source C ++ mais déplacée dans un fichier C textuellement, a commencé à renvoyer des résultats incorrects. Le MVE ci-dessous permet de reproduire le problème avec GCC. Cependant, quand j'ai, sur un coup de tête, compilé l'exemple avec Clang (et plus tard avec VS), j'ai obtenu un résultat différent! Je ne peux pas comprendre si cela doit être traité comme un bogue dans l'un des compilateurs, ou comme une manifestation d'un résultat indéfini autorisé par la norme C ou C ++. Étrangement, aucun des compilateurs ne m'a donné d'avertissement sur l'expression.
Le coupable est cette expression:
ctl.b.p52 << 12;
Ici, p52
est tapé comme uint64_t
; il fait également partie d'un syndicat (voir control_t
ci - dessous). L'opération de décalage ne perd aucune donnée car le résultat tient toujours en 64 bits. Cependant, GCC décide alors de tronquer le résultat à 52 bits si j'utilise le compilateur C ! Avec le compilateur C ++, les 64 bits de résultat sont conservés.
Pour illustrer cela, l'exemple de programme ci-dessous compile deux fonctions avec des corps identiques, puis compare leurs résultats. c_behavior()
est placé dans un fichier source C et cpp_behavior()
dans un fichier C ++, et main()
fait la comparaison.
Référentiel avec l'exemple de code: https://github.com/grigory-rechistov/c-cpp-bitfields
L'en-tête common.h définit une union de champs binaires larges de 64 bits et d'entier et déclare deux fonctions:
#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
Les fonctions ont des corps identiques, sauf que l'une est traitée en C et l'autre en 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;
}
principal 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 montre la différence entre les résultats qu'ils renvoient:
$ gcc -Wpedantic main.c c-part.c cpp-part.cpp
$ ./a.exe
OMG C != C++
Cependant, avec Clang C et C ++ se comportent de manière identique et comme prévu:
$ clang -Wpedantic main.c c-part.c cpp-part.cpp
$ ./a.exe
C == C++
Avec Visual Studio, j'obtiens le même résultat qu'avec 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++
J'ai essayé les exemples sous Windows, même si le problème d'origine avec GCC a été découvert sous Linux.
main.c
et provoque probablement un comportement indéfini de plusieurs manières. OMI, il serait plus clair de publier un MRE à fichier unique qui produit une sortie différente lors de la compilation avec chaque compilateur. Parce que l'interopérabilité C-C ++ n'est pas bien spécifiée par la norme. Notez également que l'alias de l'union provoque UB en C ++.