Chỉnh sửa 2 :
Tôi đã gỡ lỗi một lỗi thử nghiệm lạ khi một chức năng trước đây nằm trong tệp nguồn C ++ nhưng đã chuyển sang nguyên văn tệp C, bắt đầu trả về kết quả không chính xác. MVE dưới đây cho phép tái tạo vấn đề với GCC. Tuy nhiên, khi tôi, trong một ý thích bất chợt, đã biên soạn ví dụ với Clang (và sau đó với VS), tôi đã nhận được một kết quả khác! Tôi không thể biết liệu có nên coi đây là một lỗi trong một trong các trình biên dịch hay là biểu hiện của kết quả không xác định được cho phép theo tiêu chuẩn C hoặc C ++. Kỳ lạ thay, không có trình biên dịch nào đưa ra cho tôi bất kỳ cảnh báo nào về biểu thức.
Thủ phạm chính là biểu hiện này:
ctl.b.p52 << 12;
Ở đây, p52
được gõ như uint64_t
; nó cũng là một phần của liên minh (xem control_t
bên dưới). Hoạt động thay đổi không mất bất kỳ dữ liệu nào vì kết quả vẫn phù hợp với 64 bit. Tuy nhiên, sau đó GCC quyết định cắt kết quả thành 52 bit nếu tôi sử dụng trình biên dịch C ! Với trình biên dịch C ++, tất cả 64 bit kết quả được giữ nguyên.
Để minh họa điều này, chương trình ví dụ dưới đây biên dịch hai hàm với các phần thân giống nhau và sau đó so sánh kết quả của chúng. c_behavior()
được đặt trong tệp nguồn C và cpp_behavior()
trong tệp C ++ và main()
thực hiện so sánh.
Kho lưu trữ với mã ví dụ: https://github.com/grigory-rechistov/c-cpp-bitfields
Tiêu đề chung.h định nghĩa một liên kết gồm các bit và số nguyên rộng 64 bit và khai báo hai hàm:
#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
Các hàm có các cơ thể giống hệt nhau, ngoại trừ một hàm được coi là C và một hàm khác là 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 chính:
#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 cho thấy sự khác biệt giữa kết quả họ trả về:
$ gcc -Wpedantic main.c c-part.c cpp-part.cpp
$ ./a.exe
OMG C != C++
Tuy nhiên, với Clang C và C ++ hoạt động giống hệt và như mong đợi:
$ clang -Wpedantic main.c c-part.c cpp-part.cpp
$ ./a.exe
C == C++
Với Visual Studio tôi nhận được kết quả tương tự như với 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++
Tôi đã thử các ví dụ trên Windows, mặc dù vấn đề ban đầu với GCC đã được phát hiện trên Linux.
main.c
và có thể gây ra hành vi không xác định theo nhiều cách. IMO sẽ rõ ràng hơn khi đăng MRE một tệp tạo ra đầu ra khác nhau khi được biên dịch với mỗi trình biên dịch. Bởi vì tiêu chuẩn C-C ++ không được chỉ định tốt bởi tiêu chuẩn. Cũng lưu ý rằng răng cưa kết hợp gây ra UB trong C ++.