Trong mã nguồn mở program I wrote, tôi đang đọc dữ liệu nhị phân (được viết bởi chương trình khác) từ tệp và xuất ints, tăng gấp đôi, và các loại dữ liệu khác. Một trong những thách thức là nó cần phải chạy trên các máy 32 bit và 64 bit của cả hai mức độ cuối cùng, điều này có nghĩa là tôi sẽ phải thực hiện khá nhiều bit ở mức độ thấp. Tôi biết một (rất) một chút về loại bí danh xảo quyệt và nghiêm ngặt và muốn chắc chắn rằng tôi đang làm việc đúng cách.Safe char punning * để tăng gấp đôi trong C
Về cơ bản, thật dễ dàng để chuyển đổi từ một char * đến một int có kích thước khác nhau:
int64_t snativeint64_t(const char *buf)
{
/* Interpret the first 8 bytes of buf as a 64-bit int */
return *(int64_t *) buf;
}
và tôi có một dàn diễn viên của chức năng hỗ trợ để trao đổi lệnh byte khi cần thiết, chẳng hạn như :
int64_t swappedint64_t(const int64_t wrongend)
{
/* Change the endianness of a 64-bit integer */
return (((wrongend & 0xff00000000000000LL) >> 56) |
((wrongend & 0x00ff000000000000LL) >> 40) |
((wrongend & 0x0000ff0000000000LL) >> 24) |
((wrongend & 0x000000ff00000000LL) >> 8) |
((wrongend & 0x00000000ff000000LL) << 8) |
((wrongend & 0x0000000000ff0000LL) << 24) |
((wrongend & 0x000000000000ff00LL) << 40) |
((wrongend & 0x00000000000000ffLL) << 56));
}
Khi chạy, chương trình sẽ dò tìm endianness của máy và gán một trong những trên để một con trỏ hàm:
int64_t (*slittleint64_t)(const char *);
if(littleendian) {
slittleint64_t = snativeint64_t;
} else {
slittleint64_t = sswappedint64_t;
}
Bây giờ, phần phức tạp xảy ra khi tôi cố gắng truyền một từ * thành gấp đôi. Tôi muốn muốn tái sử dụng mã endian-trao đổi như sau:
union
{
double d;
int64_t i;
} int64todouble;
int64todouble.i = slittleint64_t(bufoffset);
printf("%lf", int64todouble.d);
Tuy nhiên, một số trình biên dịch có thể tối ưu hóa đi những "int64todouble.i" phân và phá vỡ các chương trình. Có cách nào an toàn hơn để thực hiện việc này không, trong khi xem xét rằng chương trình này phải được tối ưu hóa cho hiệu suất và tôi cũng muốn viết một tập song song các phép biến đổi để truyền char * thành gấp đôi trực tiếp? Nếu phương pháp hợp nhất của punning là an toàn, tôi có nên viết lại các chức năng của tôi như snativeint64_t để sử dụng nó không?
tôi đã kết thúc bằng Steve Jessop's câu trả lời vì các chức năng chuyển đổi lại bằng văn bản để sử dụng memcpy, như vậy:
int64_t snativeint64_t(const char *buf)
{
/* Interpret the first 8 bytes of buf as a 64-bit int */
int64_t output;
memcpy(&output, buf, 8);
return output;
}
biên dịch vào cùng lắp ráp chính xác như mã ban đầu của tôi:
snativeint64_t:
movq (%rdi), %rax
ret
Trong số hai, phiên bản memcpy rõ ràng hơn thể hiện những gì tôi đang cố gắng làm và nên làm việc ngay cả những trình biên dịch ngây thơ nhất.
Adam, câu trả lời của bạn cũng tuyệt vời và tôi đã học được rất nhiều từ nó. Cảm ơn vì đăng!
Việc chuyển đổi số nguyên chỉ an toàn nếu con trỏ char được căn chỉnh đủ tốt. –
Trong chương trình này, nó sẽ luôn luôn được. –
tại sao phát hiện endian-ness ở thời gian chạy? Tôi sẽ tưởng tượng chương trình chỉ có thể làm việc trên vòm nó đã được biên dịch mà sẽ có một endian-ness cụ thể (tôi biết một số cho phép bạn chọn), vậy tại sao không làm như ntohl và như thế và làm cho nó một quyết định thời gian biên dịch? –