Có một cách lập trình để phát hiện xem bạn đang ở trên một kiến trúc cuối lớn hay cuối nhỏ? Tôi cần có khả năng viết mã sẽ thực thi trên hệ thống Intel hoặc PPC và sử dụng chính xác cùng một mã (tức là không có biên dịch có điều kiện).
Có một cách lập trình để phát hiện xem bạn đang ở trên một kiến trúc cuối lớn hay cuối nhỏ? Tôi cần có khả năng viết mã sẽ thực thi trên hệ thống Intel hoặc PPC và sử dụng chính xác cùng một mã (tức là không có biên dịch có điều kiện).
Câu trả lời:
Tôi không thích phương pháp dựa trên kiểu xảo quyệt - nó thường sẽ bị cảnh báo bởi trình biên dịch. Đó chính xác là những gì công đoàn dành cho!
bool is_big_endian(void)
{
union {
uint32_t i;
char c[4];
} bint = {0x01020304};
return bint.c[0] == 1;
}
Nguyên tắc này tương đương với trường hợp loại theo đề xuất của người khác, nhưng điều này rõ ràng hơn - và theo C99, được đảm bảo là chính xác. gcc thích điều này so với các con trỏ trực tiếp đúc.
Điều này cũng tốt hơn nhiều so với việc sửa lỗi endian tại thời gian biên dịch - đối với HĐH hỗ trợ đa kiến trúc (ví dụ như nhị phân chất béo trên Mac os x), điều này sẽ hoạt động cho cả ppc / i386, trong khi đó rất dễ làm hỏng mọi thứ .
CHAR_BIT != 8
?
Bạn có thể làm điều đó bằng cách thiết lập một int và che giấu các bit, nhưng có lẽ cách dễ nhất là sử dụng ops chuyển đổi byte mạng tích hợp (vì thứ tự byte mạng luôn luôn là endian lớn).
if ( htonl(47) == 47 ) {
// Big endian
} else {
// Little endian.
}
Bit fiddling có thể nhanh hơn, nhưng cách này rất đơn giản, dễ hiểu và khá khó để gây rối.
BSWAP
hoạt động.
Xin vui lòng xem bài viết này :
Dưới đây là một số mã để xác định loại máy của bạn là gì
int num = 1; if(*(char *)&num == 1) { printf("\nLittle-Endian\n"); } else { printf("Big-Endian\n"); }
Bạn có thể dùng std::endian
nếu bạn có quyền truy cập vào trình biên dịch C ++ 20 như GCC 8+ hoặc Clang 7+.
Lưu ý: std::endian
đã bắt đầu <type_traits>
nhưng đã được chuyển đến <bit>
tại cuộc họp tại Cologne 2019. GCC 8, Clang 7, 8 và 9 có nó trong <type_traits>
khi GCC 9+ và Clang 10+ có nó trong <bit>
.
#include <bit>
if constexpr (std::endian::native == std::endian::big)
{
// Big endian system
}
else if constexpr (std::endian::native == std::endian::little)
{
// Little endian system
}
else
{
// Something else
}
Điều này thường được thực hiện tại thời gian biên dịch (đặc biệt vì lý do hiệu suất) bằng cách sử dụng các tệp tiêu đề có sẵn từ trình biên dịch hoặc tạo riêng của bạn. Trên linux, bạn có tệp tiêu đề "/usr/include/endian.h"
Tôi ngạc nhiên không ai đã đề cập đến các macro mà bộ xử lý trước định nghĩa theo mặc định. Trong khi những điều này sẽ thay đổi tùy thuộc vào nền tảng của bạn; họ sạch sẽ hơn nhiều so với việc phải viết kiểm tra endian của riêng bạn.
Ví dụ; nếu chúng ta xem các macro tích hợp mà GCC định nghĩa (trên máy X86-64):
:| gcc -dM -E -x c - |grep -i endian
#define __LITTLE_ENDIAN__ 1
Trên máy PPC tôi nhận được:
:| gcc -dM -E -x c - |grep -i endian
#define __BIG_ENDIAN__ 1
#define _BIG_ENDIAN 1
( :| gcc -dM -E -x c -
Phép thuật in ra tất cả các macro tích hợp).
echo "\n" | gcc -x c -E -dM - |& grep -i 'endian'
không trả về gì, trong khi gcc 3.4.3 (từ /usr/sfw/bin
dù sao) trong Solaris có định nghĩa dọc theo các dòng này. Tôi đã thấy các vấn đề tương tự trên VxWorks Tornado (gcc 2.95) -vs- VxWorks Workbench (gcc 3.4.4).
Ừm ... Thật ngạc nhiên khi không ai nhận ra rằng trình biên dịch sẽ đơn giản tối ưu hóa thử nghiệm và sẽ đặt một kết quả cố định làm giá trị trả về. Điều này làm cho tất cả các ví dụ mã ở trên, vô dụng một cách hiệu quả. Điều duy nhất sẽ được trả lại là sự kết thúc vào thời gian biên dịch! Và vâng, tôi đã thử nghiệm tất cả các ví dụ trên. Đây là một ví dụ với MSVC 9.0 (Visual Studio 2008).
Mã C tinh khiết
int32 DNA_GetEndianness(void)
{
union
{
uint8 c[4];
uint32 i;
} u;
u.i = 0x01020304;
if (0x04 == u.c[0])
return DNA_ENDIAN_LITTLE;
else if (0x01 == u.c[0])
return DNA_ENDIAN_BIG;
else
return DNA_ENDIAN_UNKNOWN;
}
Tháo gỡ
PUBLIC _DNA_GetEndianness
; Function compile flags: /Ogtpy
; File c:\development\dna\source\libraries\dna\endian.c
; COMDAT _DNA_GetEndianness
_TEXT SEGMENT
_DNA_GetEndianness PROC ; COMDAT
; 11 : union
; 12 : {
; 13 : uint8 c[4];
; 14 : uint32 i;
; 15 : } u;
; 16 :
; 17 : u.i = 1;
; 18 :
; 19 : if (1 == u.c[0])
; 20 : return DNA_ENDIAN_LITTLE;
mov eax, 1
; 21 : else if (1 == u.c[3])
; 22 : return DNA_ENDIAN_BIG;
; 23 : else
; 24 : return DNA_ENDIAN_UNKNOWN;
; 25 : }
ret
_DNA_GetEndianness ENDP
END
Có lẽ có thể tắt bất kỳ tối ưu hóa thời gian biên dịch nào cho chỉ chức năng này, nhưng tôi không biết. Nếu không, có thể mã hóa cứng trong lắp ráp, mặc dù đó không phải là di động. Và thậm chí sau đó thậm chí có thể được tối ưu hóa. Nó làm cho tôi nghĩ rằng tôi cần một số trình biên dịch thực sự nhảm nhí, thực hiện cùng một mã cho tất cả các CPU / bộ hướng dẫn hiện có, và .... đừng bận tâm.
Ngoài ra, một người nào đó ở đây nói rằng endianness không thay đổi trong thời gian chạy. SAI LẦM. Có máy móc bi-endian ra khỏi đó. Endianness của họ có thể thay đổi thực hiện Durng. CSONG, không chỉ có Little Endian và Big Endian, mà còn có các endian khác (thật là một từ).
Tôi ghét và yêu mã hóa cùng một lúc ...
Khai báo một biến int:
int variable = 0xFF;
Bây giờ sử dụng con trỏ char * đến các phần khác nhau của nó và kiểm tra xem những gì trong các phần đó.
char* startPart = reinterpret_cast<char*>( &variable );
char* endPart = reinterpret_cast<char*>( &variable ) + sizeof( int ) - 1;
Tùy thuộc vào cái nào trỏ đến byte 0xFF bây giờ, bạn có thể phát hiện tuổi thọ. Điều này đòi hỏi sizeof (int)> sizeof (char), nhưng nó hoàn toàn đúng với các nền tảng được thảo luận.
Để biết thêm chi tiết, bạn có thể muốn xem bài viết về bảng mã này Các khái niệm cơ bản về Endianness :
Làm thế nào để tự động kiểm tra loại Endian trong thời gian chạy?
Như đã giải thích trong Câu hỏi thường gặp về Hoạt hình máy tính, bạn có thể sử dụng chức năng sau để xem mã của bạn đang chạy trên hệ thống Little- hay Big-Endian: Thu gọn
#define BIG_ENDIAN 0 #define LITTLE_ENDIAN 1
int TestByteOrder()
{
short int word = 0x0001;
char *byte = (char *) &word;
return(byte[0] ? LITTLE_ENDIAN : BIG_ENDIAN);
}
Mã này gán giá trị 0001h cho số nguyên 16 bit. Sau đó, một con trỏ char được gán cho điểm tại byte đầu tiên (ít quan trọng nhất) của giá trị số nguyên. Nếu byte đầu tiên của số nguyên là 0x01h, thì hệ thống là Little-Endian (0x01h nằm ở địa chỉ thấp nhất hoặc ít quan trọng nhất). Nếu là 0x00h thì hệ thống là Big-Endian.
Cách C ++ đã được sử dụng boost , trong đó các kiểm tra và phôi tiền xử lý được ngăn cách bên trong các thư viện được kiểm tra rất kỹ lưỡng.
Thư viện Predef (boost / preef.h) nhận ra bốn loại endianness khác nhau .
Các Endian Thư viện được lên kế hoạch để trình tiêu chuẩn C ++, và hỗ trợ một loạt các hoạt động trên dữ liệu endian nhạy cảm.
Như đã nêu trong các câu trả lời ở trên, Endianness sẽ là một phần của c ++ 20.
Trừ khi bạn đang sử dụng khung được chuyển sang bộ xử lý PPC và Intel, bạn sẽ phải thực hiện các biên dịch có điều kiện, vì các nền tảng PPC và Intel có kiến trúc phần cứng, đường ống, thanh cái, v.v. cả hai.
Đối với việc tìm kiếm endianness, hãy làm như sau:
short temp = 0x1234;
char* tempChar = (char*)&temp;
Bạn sẽ nhận được tempChar là 0x12 hoặc 0x34, từ đó bạn sẽ biết được tuổi thọ.
stdint.h
và sử dụng int16_t
để chứng minh trong tương lai chống lại sự khác biệt ngắn trên nền tảng khác.
Tôi sẽ làm một cái gì đó như thế này:
bool isBigEndian() {
static unsigned long x(1);
static bool result(reinterpret_cast<unsigned char*>(&x)[0] == 0);
return result;
}
Dọc theo những dòng này, bạn sẽ có được một hàm hiệu quả về thời gian chỉ thực hiện phép tính một lần.
Như đã nêu ở trên, sử dụng thủ đoạn công đoàn.
Mặc dù vậy, có một số vấn đề với những vấn đề được khuyên ở trên, đáng chú ý nhất là việc truy cập bộ nhớ không được phân bổ nổi tiếng là chậm đối với hầu hết các kiến trúc, và một số trình biên dịch thậm chí sẽ không nhận ra các vị từ không đổi như vậy, trừ khi được căn chỉnh từ.
Bởi vì chỉ kiểm tra endian là nhàm chán, nên ở đây hàm (mẫu) sẽ lật đầu vào / đầu ra của số nguyên tùy ý theo thông số kỹ thuật của bạn, bất kể kiến trúc máy chủ.
#include <stdint.h>
#define BIG_ENDIAN 1
#define LITTLE_ENDIAN 0
template <typename T>
T endian(T w, uint32_t endian)
{
// this gets optimized out into if (endian == host_endian) return w;
union { uint64_t quad; uint32_t islittle; } t;
t.quad = 1;
if (t.islittle ^ endian) return w;
T r = 0;
// decent compilers will unroll this (gcc)
// or even convert straight into single bswap (clang)
for (int i = 0; i < sizeof(r); i++) {
r <<= 8;
r |= w & 0xff;
w >>= 8;
}
return r;
};
Sử dụng:
Để chuyển đổi từ endian đã cho sang máy chủ, hãy sử dụng:
host = endian(source, endian_of_source)
Để chuyển đổi từ endian host sang endian đã cho, hãy sử dụng:
output = endian(hostsource, endian_you_want_to_output)
Mã kết quả nhanh như viết tay trên clang, trên gcc nó chậm hơn (không được kiểm soát &, <<, >>, | cho mỗi byte) nhưng vẫn ổn.
bool isBigEndian()
{
static const uint16_t m_endianCheck(0x00ff);
return ( *((uint8_t*)&m_endianCheck) == 0x0);
}
#define IS_BIGENDIAN() (*((char*) &((int){ 0x00ff })) == (0x00))
Không sử dụng a union
!
C ++ không cho phép loại picky qua union
s!
Đọc từ một lĩnh vực công đoàn không phải là lĩnh vực cuối cùng được viết là hành vi không xác định !
Nhiều trình biên dịch hỗ trợ làm như một phần mở rộng, nhưng ngôn ngữ không đảm bảo.
Xem câu trả lời này để biết thêm chi tiết:
https://stackoverflow.com/a/11996970
Chỉ có hai câu trả lời hợp lệ được đảm bảo là có thể mang theo được.
Câu trả lời đầu tiên, nếu bạn có quyền truy cập vào một hệ thống hỗ trợ C ++ 20,
là sử dụng std::endian
từ <type_traits>
tiêu đề.
(Tại thời điểm viết bài, C ++ 20 vẫn chưa được phát hành, nhưng trừ khi có điều gì đó xảy ra ảnh hưởng std::endian
sự bao gồm của nó, đây sẽ là cách ưa thích để kiểm tra độ bền trong thời gian biên dịch từ C ++ 20 trở đi.)
constexpr bool is_little_endian = (std::endian::native == std::endian::little);
Trước C ++ 20, câu trả lời hợp lệ duy nhất là lưu trữ một số nguyên và sau đó kiểm tra byte đầu tiên của nó thông qua kiểu pucky.
Không giống như việc sử dụng union
s, điều này được hệ thống loại của C ++ cho phép rõ ràng.
Điều quan trọng cần nhớ là static_cast
nên sử dụng tính di động tối ưu ,
bởi vì reinterpret_cast
việc triển khai được xác định.
Nếu một chương trình cố gắng truy cập giá trị được lưu trữ của một đối tượng thông qua một giá trị khác với một trong các loại sau đây thì hành vi không được xác định: ... a
char
hoặcunsigned char
loại.
enum class endianness
{
little = 0,
big = 1,
};
inline endianness get_system_endianness()
{
const int value { 0x01 };
const void * address = static_cast<const void *>(&value);
const unsigned char * least_significant_address = static_cast<const unsigned char *>(address);
return (*least_significant_address == 0x01) ? endianness::little : endianness::big;
}
inline bool is_system_little_endian()
{
const int value { 0x01 };
const void * address = static_cast<const void *>(&value);
const unsigned char * least_significant_address = static_cast<const unsigned char *>(address);
return (*least_significant_address == 0x01);
}
inline bool is_system_little_endian()
{
const int value = 0x01;
const void * address = static_cast<const void *>(&value);
const unsigned char * least_significant_address = static_cast<const unsigned char *>(address);
return (*least_significant_address == 0x01);
}
chưa được kiểm tra, nhưng trong tâm trí của tôi, điều này nên làm việc? Vì nó sẽ là 0x01 trên endian nhỏ và 0x00 trên endian lớn?
bool runtimeIsLittleEndian(void)
{
volatile uint16_t i=1;
return ((uint8_t*)&i)[0]==0x01;//0x01=little, 0x00=big
}
Khai báo:
biên dịch thời gian, không vĩ mô, giải pháp constexpr C ++ 11:
union {
uint16_t s;
unsigned char c[2];
} constexpr static d {1};
constexpr bool is_little_endian() {
return d.c[0] == 1;
}
Bạn cũng có thể thực hiện việc này thông qua bộ tiền xử lý bằng cách sử dụng một cái gì đó như tệp tiêu đề boost có thể được tìm thấy boost endian
Trừ khi tiêu đề cuối chỉ là GCC, nó cung cấp các macro bạn có thể sử dụng.
#include "endian.h"
...
if (__BYTE_ORDER == __LITTLE_ENDIAN) { ... }
else if (__BYTE_ORDER == __BIG_ENDIAN) { ... }
else { throw std::runtime_error("Sorry, this version does not support PDP Endian!");
...
__BYTE_ORDER__
, __ORDER_LITTLE_ENDIAN__
và __ORDER_BIG_ENDIAN__
?
Nếu bạn không muốn biên dịch có điều kiện, bạn chỉ có thể viết mã độc lập về cuối. Dưới đây là một ví dụ (lấy từ Rob Pike ):
Đọc một số nguyên được lưu trữ trong endian nhỏ trên đĩa, theo cách độc lập về cuối:
i = (data[0]<<0) | (data[1]<<8) | (data[2]<<16) | (data[3]<<24);
Cùng một mã, cố gắng tính đến độ bền của máy:
i = *((int*)data);
#ifdef BIG_ENDIAN
/* swap the bytes */
i = ((i&0xFF)<<24) | (((i>>8)&0xFF)<<16) | (((i>>16)&0xFF)<<8) | (((i>>24)&0xFF)<<0);
#endif
Đây là một phiên bản C khác. Nó định nghĩa một macro được gọi wicked_cast()
cho phép loại bỏ nội tuyến thông qua các ký tự liên kết C99 và __typeof__
toán tử không chuẩn .
#include <limits.h>
#if UCHAR_MAX == UINT_MAX
#error endianness irrelevant as sizeof(int) == 1
#endif
#define wicked_cast(TYPE, VALUE) \
(((union { __typeof__(VALUE) src; TYPE dest; }){ .src = VALUE }).dest)
_Bool is_little_endian(void)
{
return wicked_cast(unsigned char, 1u);
}
Nếu số nguyên là các giá trị byte đơn, thì endianness không có nghĩa và lỗi thời gian biên dịch sẽ được tạo.
Các trình biên dịch C cách (ít nhất là tất cả mọi người tôi biết) làm việc endianness đã được quyết định tại thời gian biên dịch. Ngay cả đối với các bộ xử lý biendian (như ARM och MIPS), bạn phải chọn endianness trong thời gian biên dịch. Hơn nữa độ bền được xác định trong tất cả các định dạng tệp phổ biến cho các tệp thực thi (chẳng hạn như ELF). Mặc dù có thể tạo một blob nhị phân của mã biandian (đối với một số máy chủ ARM có thể khai thác?) Có lẽ nó phải được thực hiện trong quá trình lắp ráp.
Như Coriiander đã chỉ ra, hầu hết (nếu không phải tất cả) các mã ở đây sẽ được tối ưu hóa vào thời gian biên dịch, vì vậy các nhị phân được tạo sẽ không kiểm tra "endianness" trong thời gian chạy.
Nó đã được quan sát thấy rằng một thực thi nhất định không nên chạy theo hai lệnh byte khác nhau, nhưng tôi không biết nếu đó luôn luôn là như vậy, và nó có vẻ như là một hack để tôi kiểm tra tại thời điểm biên dịch. Vì vậy, tôi đã mã hóa chức năng này:
#include <stdint.h>
int* _BE = 0;
int is_big_endian() {
if (_BE == 0) {
uint16_t* teste = (uint16_t*)malloc(4);
*teste = (*teste & 0x01FE) | 0x0100;
uint8_t teste2 = ((uint8_t*) teste)[0];
free(teste);
_BE = (int*)malloc(sizeof(int));
*_BE = (0x01 == teste2);
}
return *_BE;
}
MinGW không thể tối ưu hóa mã này, mặc dù nó tối ưu hóa các mã khác ở đây. Tôi tin rằng đó là vì tôi để giá trị "ngẫu nhiên" được sắp xếp trên bộ nhớ byte nhỏ hơn (ít nhất là 7 bit của nó), vì vậy trình biên dịch không thể biết giá trị ngẫu nhiên đó là gì và nó không tối ưu hóa Các chức năng đi.
Tôi cũng đã mã hóa hàm để việc kiểm tra chỉ được thực hiện một lần và giá trị trả về được lưu trữ cho các lần kiểm tra tiếp theo.
0x7FE
? Tại sao lại sử dụng malloc()
? Điều đó thật lãng phí. Và _BE
là một rò rỉ bộ nhớ (mặc dù nhỏ) và một điều kiện cuộc đua đang chờ xảy ra, những lợi ích của việc lưu trữ kết quả một cách linh hoạt không đáng để gặp rắc rối. Tôi sẽ làm một cái gì đó giống như thế này thay vào đó: static const uint16_t teste = 1; int is_little_endian() { return (0x01 == ((uint8_t*)&teste)[0]); } int is_big_endian() { return (0x01 == ((uint8_t*)&teste)[1]); }
Đơn giản và hiệu quả, và ít công việc hơn để thực hiện trong thời gian chạy.
volatile
, hoặc #pragma
, v.v.
Xem Endianness - Minh họa mã cấp C.
// assuming target architecture is 32-bit = 4-Bytes
enum ENDIANNESS{ LITTLEENDIAN , BIGENDIAN , UNHANDLE };
ENDIANNESS CheckArchEndianalityV1( void )
{
int Endian = 0x00000001; // assuming target architecture is 32-bit
// as Endian = 0x00000001 so MSB (Most Significant Byte) = 0x00 and LSB (Least Significant Byte) = 0x01
// casting down to a single byte value LSB discarding higher bytes
return (*(char *) &Endian == 0x01) ? LITTLEENDIAN : BIGENDIAN;
}
Tôi đã xem qua sách giáo khoa: Hệ thống máy tính: quan điểm của một lập trình viên , và có một vấn đề để xác định đây là chương trình cuối nào của chương trình C.
Tôi đã sử dụng tính năng của con trỏ để làm điều đó như sau:
#include <stdio.h>
int main(void){
int i=1;
unsigned char* ii = &i;
printf("This computer is %s endian.\n", ((ii[0]==1) ? "little" : "big"));
return 0;
}
Vì int chiếm 4 byte và char chỉ chiếm 1 byte. Chúng ta có thể sử dụng một con trỏ char để trỏ đến int có giá trị 1. Do đó, nếu máy tính là endian nhỏ, char mà con trỏ char trỏ tới là với giá trị 1, nếu không, giá trị của nó phải là 0.