C Định nghĩa macro để xác định máy endian lớn hay endian nhỏ?


107

Có định nghĩa macro một dòng để xác định độ bền của máy không. Tôi đang sử dụng mã sau nhưng chuyển đổi nó thành macro sẽ quá dài.

unsigned char test_endian( void )
{
    int test_var = 1;
    unsigned char *test_endian = (unsigned char*)&test_var;

    return (test_endian[0] == 0);
}

2
Tại sao không bao gồm cùng một mã vào một macro?
răng nhọn vào

4
Bạn không thể xác định khả năng tồn tại một mình với bộ tiền xử lý C. Bạn cũng muốn 0thay vì NULLtrong bài kiểm tra cuối cùng của mình và thay đổi một trong các test_endianđối tượng thành thứ khác :-).
Alok Singhal

2
Ngoài ra, tại sao một macro là cần thiết? Hàm nội tuyến sẽ hoạt động tương tự và an toàn hơn nhiều.
sharptooth,

13
@Sharptooth, một macro hấp dẫn vì giá trị của nó có thể được biết vào thời điểm biên dịch, có nghĩa là bạn có thể sử dụng khả năng sẵn có của nền tảng để kiểm soát việc khởi tạo mẫu, hoặc thậm chí có thể chọn các khối mã khác nhau bằng một #ifchỉ thị.
Rob Kennedy

3
Điều đó đúng, nhưng không hiệu quả. Nếu tôi có một cpu endian nhỏ và tôi đang ghi dữ liệu endian nhỏ vào dây hoặc vào một tệp, tôi muốn tránh giải nén và đóng gói lại dữ liệu mà không có mục đích gì. Tôi đã từng viết trình điều khiển video để kiếm sống. Điều cực kỳ quan trọng khi ghi pixel vào card màn hình là tối ưu hóa mọi nơi bạn có thể.
Edward Falk

Câu trả lời:


102

Mã hỗ trợ các thứ tự byte tùy ý, sẵn sàng được đưa vào một tệp có tên order32.h:

#ifndef ORDER32_H
#define ORDER32_H

#include <limits.h>
#include <stdint.h>

#if CHAR_BIT != 8
#error "unsupported char size"
#endif

enum
{
    O32_LITTLE_ENDIAN = 0x03020100ul,
    O32_BIG_ENDIAN = 0x00010203ul,
    O32_PDP_ENDIAN = 0x01000302ul,      /* DEC PDP-11 (aka ENDIAN_LITTLE_WORD) */
    O32_HONEYWELL_ENDIAN = 0x02030001ul /* Honeywell 316 (aka ENDIAN_BIG_WORD) */
};

static const union { unsigned char bytes[4]; uint32_t value; } o32_host_order =
    { { 0, 1, 2, 3 } };

#define O32_HOST_ORDER (o32_host_order.value)

#endif

Bạn sẽ kiểm tra các hệ thống endian nhỏ thông qua

O32_HOST_ORDER == O32_LITTLE_ENDIAN

11
Mặc dù vậy, điều này không cho phép bạn quyết định endian-ness cho đến thời gian chạy. Sau đây không thể biên dịch bởi vì. / ** isLittleEndian :: result -> 0 hoặc 1 * / struct isLittleEndian {enum isLittleEndianResult {result = (O32_HOST_ORDER == O32_LITTLE_ENDIAN)}; }
user48956

3
Có thể nhận được kết quả cho đến thời gian chạy không?
k06a

8
Tại sao char? Sử dụng tốt hơn uint8_tvà không thành công nếu loại này không có sẵn (có thể được kiểm tra bằng cách #if UINT8_MAX). Lưu ý rằng CHAR_BITđộc lập với uint8_t.
Andreas Spindler,


3
Hãy để tôi tung thêm một cái nữa vào hỗn hợp, để có sự hoàn chỉnh:O32_HONEYWELL_ENDIAN = 0x02030001ul /* Honeywell 316 */
Edward Falk

49

Nếu bạn có một trình biên dịch hỗ trợ các ký tự ghép C99:

#define IS_BIG_ENDIAN (!*(unsigned char *)&(uint16_t){1})

hoặc là:

#define IS_BIG_ENDIAN (!(union { uint16_t u16; unsigned char c; }){ .u16 = 1 }.c)

Tuy nhiên, nói chung, bạn nên cố gắng viết mã không phụ thuộc vào độ bền của nền tảng máy chủ.


Ví dụ về việc triển khai độc lập với máy chủ lưu trữ ntohl():

uint32_t ntohl(uint32_t n)
{
    unsigned char *np = (unsigned char *)&n;

    return ((uint32_t)np[0] << 24) |
        ((uint32_t)np[1] << 16) |
        ((uint32_t)np[2] << 8) |
        (uint32_t)np[3];
}

3
"bạn nên cố gắng viết mã không phụ thuộc vào độ bền của nền tảng máy chủ lưu trữ". Thật không may, lời cầu xin của tôi, "Tôi biết chúng tôi đang viết một lớp tương thích POSIX, nhưng tôi không muốn triển khai ntoh, vì nó phụ thuộc vào độ bền của nền tảng máy chủ" luôn rơi vào tai điếc ;-). Xử lý định dạng đồ họa và mã chuyển đổi là ứng cử viên chính khác mà tôi đã thấy - bạn không muốn mọi thứ luôn luôn gọi ntohl.
Steve Jessop,

5
Bạn có thể triển khai ntohltheo cách không phụ thuộc vào độ bền của nền tảng máy chủ.
caf

1
@caf Bạn sẽ viết ntohl theo cách độc lập với host-endianness như thế nào?
Hayri Uğur Koltuk

3
@AliVeli: Tôi đã thêm một ví dụ triển khai vào câu trả lời.
caf

6
Tôi cũng nên thêm vào bản ghi rằng "(* (uint16_t *)" \ 0 \ xff "<0x100)" sẽ không biên dịch thành một hằng số, bất kể tôi tối ưu hóa bao nhiêu, ít nhất là với gcc 4.5.2. Nó luôn tạo mã thực thi.
Edward Falk

43

Không có tiêu chuẩn nào, nhưng trên nhiều hệ thống bao gồm <endian.h>sẽ cung cấp cho bạn một số định nghĩa để tìm kiếm.


30
Kiểm tra độ bền với #if __BYTE_ORDER == __LITTLE_ENDIAN#elif __BYTE_ORDER == __BIG_ENDIAN. Và tạo ra một cách #errorkhác.
To1ne

6
<endian.h>không có sẵn trên Windows
gỉyx

2
Các dự án AndroidChromium sử dụng endian.htrừ khi __APPLE__hoặc _WIN32được xác định.
patryk.beza

1
Trong OpenBSD 6.3, <endian.h> cung cấp #if BYTE_ORDER == LITTLE_ENDIAN(hoặc BIG_ENDIAN) không có dấu gạch dưới trước tên. _BYTE_ORDERchỉ dành cho tiêu đề hệ thống. __BYTE_ORDERkhông tồn tại.
George Koehler

@ To1ne Tôi nghi ngờ rằng Endianness có liên quan đến Windows, vì Windows (ít nhất là hiện tại) chỉ chạy trên các máy x86 và ARM. x86 luôn là LE và ARM có thể được cấu hình để sử dụng một trong hai kiến ​​trúc.
SimonC

27

Để phát hiện độ bền tại thời gian chạy, bạn phải tham khảo bộ nhớ. Nếu bạn tuân theo tiêu chuẩn C, việc khai báo một biến trong bộ nhớ yêu cầu một câu lệnh, nhưng trả về một giá trị thì yêu cầu một biểu thức. Tôi không biết cách thực hiện việc này trong một macro duy nhất — đây là lý do tại sao gcc có phần mở rộng :-)

Nếu bạn muốn có tệp .h, bạn có thể xác định

static uint32_t endianness = 0xdeadbeef; 
enum endianness { BIG, LITTLE };

#define ENDIANNESS ( *(const char *)&endianness == 0xef ? LITTLE \
                   : *(const char *)&endianness == 0xde ? BIG \
                   : assert(0))

và sau đó bạn có thể sử dụng ENDIANNESSmacro theo ý muốn.


6
Tôi thích điều này vì nó thừa nhận sự tồn tại của sự bền vững khác với nhỏ và lớn.
Alok Singhal

6
Nói về điều này, có thể nên gọi macro INT_ENDIANNESS hoặc thậm chí là UINT32_T_ENDIANNESS, vì nó chỉ kiểm tra biểu diễn bộ nhớ của một loại. Có một ARM ABI trong đó các loại tích phân là little-endian, nhưng double là middle-endian (mỗi từ là little-endian, nhưng từ có dấu bit trong nó đứng trước từ còn lại). Điều đó đã gây ra một số sự phấn khích trong nhóm biên dịch trong một ngày hoặc lâu hơn, tôi có thể cho bạn biết.
Steve Jessop,

19

Nếu bạn muốn chỉ dựa vào bộ xử lý trước, bạn phải tìm ra danh sách các ký hiệu được xác định trước. Số học tiền xử lý không có khái niệm địa chỉ.

GCC trên Mac xác định __LITTLE_ENDIAN__hoặc__BIG_ENDIAN__

$ gcc -E -dM - < /dev/null |grep ENDIAN
#define __LITTLE_ENDIAN__ 1

Sau đó, bạn có thể thêm nhiều chỉ thị có điều kiện tiền xử lý hơn dựa trên phát hiện nền tảng như #ifdef _WIN32v.v.


6
GCC 4.1.2 trên Linux dường như không xác định các macro đó, mặc dù GCC 4.0.1 và 4.2.1 xác định chúng trên Macintosh. Vì vậy, nó không phải là một phương pháp đáng tin cậy để phát triển đa nền tảng, ngay cả khi bạn được phép ra lệnh sử dụng trình biên dịch nào.
Rob Kennedy

1
oh yeah, đó là vì nó chỉ được xác định bởi GCC trên Mac.
Gregory Pakosz

Lưu ý: GCC của tôi (trên Mac) xác định #define __BIG_ENDIAN__ 1#define _BIG_ENDIAN 1.

clang 5.0.1 cho OpenBSD / amd64 có #define __LITTLE_ENDIAN__ 1. Macro này dường như là một tính năng clang, không phải là một tính năng gcc. Các gcclệnh trong một số máy Mac không phải là gcc, nó kêu vang.
George Koehler

GCC 4.2.1 trên Mac là GCC hồi đó
Gregory Pakosz

15

Tôi tin rằng đây là những gì đã được yêu cầu. Tôi chỉ thử nghiệm điều này trên một máy endian nhỏ dưới msvc. Ai đó làm ơn xác nhận trên một máy endian lớn.

    #define LITTLE_ENDIAN 0x41424344UL 
    #define BIG_ENDIAN    0x44434241UL
    #define PDP_ENDIAN    0x42414443UL
    #define ENDIAN_ORDER  ('ABCD') 

    #if ENDIAN_ORDER==LITTLE_ENDIAN
        #error "machine is little endian"
    #elif ENDIAN_ORDER==BIG_ENDIAN
        #error "machine is big endian"
    #elif ENDIAN_ORDER==PDP_ENDIAN
        #error "jeez, machine is PDP!"
    #else
        #error "What kind of hardware is this?!"
    #endif

Một lưu ý phụ (dành riêng cho trình biên dịch), với một trình biên dịch tích cực, bạn có thể sử dụng tối ưu hóa "loại bỏ mã chết" để đạt được hiệu quả tương tự như thời gian biên dịch #ifnhư sau:

    unsigned yourOwnEndianSpecific_htonl(unsigned n)
    {
        static unsigned long signature= 0x01020304UL; 
        if (1 == (unsigned char&)signature) // big endian
            return n;
        if (2 == (unsigned char&)signature) // the PDP style
        {
            n = ((n << 8) & 0xFF00FF00UL) | ((n>>8) & 0x00FF00FFUL);
            return n;
        }
        if (4 == (unsigned char&)signature) // little endian
        {
            n = (n << 16) | (n >> 16);
            n = ((n << 8) & 0xFF00FF00UL) | ((n>>8) & 0x00FF00FFUL);
            return n;
        }
        // only weird machines get here
        return n; // ?
    }

Ở trên dựa trên thực tế là trình biên dịch nhận ra các giá trị không đổi tại thời điểm biên dịch, loại bỏ hoàn toàn mã bên trong if (false) { ... }và thay thế mã như if (true) { foo(); }với foo();Trường hợp xấu nhất: trình biên dịch không thực hiện tối ưu hóa, bạn vẫn nhận được mã chính xác nhưng chậm hơn một chút.


Tôi thích phương pháp này, nhưng hãy sửa cho tôi nếu tôi sai: điều này chỉ hoạt động khi bạn đang biên dịch trên máy mà bạn đang xây dựng, đúng không?
leetNightshade

3
gcc cũng gây ra lỗi do hằng số ký tự nhiều ký tự. Như vậy, không phải xách tay.
Edward Falk

2
trình biên dịch nào cho phép bạn viết 'ABCD'?
Ryan Haining

2
Nhiều trình biên dịch sẽ cho phép các hằng số ký tự đa byte trong các chế độ tuân thủ thoải mái, nhưng chạy phần trên cùng clang -Wpedantic -Werror -Wall -ansi foo.cvà nó sẽ bị lỗi. (Clang và cụ thể là điều này -Wfour-char-constants -Werror:)

@Edward Falk Không có lỗi khi có một hằng số nhiều ký tự trong mã. Đó là hành vi được xác định bởi thực thi C11 6.4.4.4. 10. gcc và khác có thể / không cảnh báo / lỗi tùy thuộc vào cài đặt, nhưng nó không phải là lỗi C. Việc sử dụng hằng số ký tự nhiều ký tự chắc chắn không phổ biến.
chux - Phục hồi Monica

10

Nếu bạn đang tìm kiếm một bài kiểm tra thời gian biên dịch và bạn đang sử dụng gcc, bạn có thể thực hiện:

#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__

Xem tài liệu gcc để biết thêm thông tin.


3
Đây chắc chắn là câu trả lời tốt nhất cho bất cứ ai sử dụng gcc
rtpax

2
__BYTE_ORDER__khả dụng kể từ GCC 4.6
Benoit Blanchon

8

Trên thực tế, bạn có thể truy cập bộ nhớ của một đối tượng tạm thời bằng cách sử dụng một ký tự ghép (C99):

#define IS_LITTLE_ENDIAN (1 == *(unsigned char *)&(const int){1})

GCC nào sẽ đánh giá tại thời điểm biên dịch.


Tôi thích nó. Có cách nào di động, có thời gian biên dịch để biết rằng bạn đang biên dịch theo C99 không?
Edward Falk

1
Ồ, và nếu đó không phải là GCC thì sao?
Edward Falk

1
@EdwardFalk Có. #if __STDC_VERSION__ >= 199901L.
Jens

7

'Thư viện mạng C' cung cấp các chức năng để xử lý endian'ness. Cụ thể là htons (), htonl (), ntohs () và ntohl () ... trong đó n là "mạng" (tức là. Big-endian) và h là "máy chủ" (tức là độ bền của máy chạy mã).

Các 'hàm' rõ ràng này (thường) được định nghĩa là macro [xem <netinet / in.h>], vì vậy không có chi phí thời gian chạy để sử dụng chúng.

Các macro sau đây sử dụng các 'chức năng' này để đánh giá mức độ cuối cùng.

#include <arpa/inet.h>
#define  IS_BIG_ENDIAN     (1 == htons(1))
#define  IS_LITTLE_ENDIAN  (!IS_BIG_ENDIAN)

Ngoài ra:

Lần duy nhất tôi cần biết độ bền của một hệ thống là khi tôi ghi ra một biến [vào một tệp / khác] có thể được đọc vào bởi một hệ thống khác không xác định về độ bền (đối với khả năng tương thích giữa các nền tảng ) ... Trong những trường hợp như vậy, bạn có thể thích sử dụng trực tiếp các hàm endian:

#include <arpa/inet.h>

#define JPEG_MAGIC  (('J'<<24) | ('F'<<16) | ('I'<<8) | 'F')

// Result will be in 'host' byte-order
unsigned long  jpeg_magic = JPEG_MAGIC;

// Result will be in 'network' byte-order (IE. Big-Endian/Human-Readable)
unsigned long  jpeg_magic = htonl(JPEG_MAGIC);

Điều này không thực sự trả lời cho câu hỏi tìm kiếm một cách nhanh chóng để xác định khả năng sinh sản.
Oren

@Oren: Đối với lời phê bình hợp lệ của bạn, tôi đã thêm vào chi tiết để giải quyết câu hỏi ban đầu trực tiếp hơn.
BlueChip

6

Sử dụng một hàm nội tuyến thay vì một macro. Bên cạnh đó, bạn cần phải lưu trữ một thứ gì đó trong bộ nhớ, đây là một tác dụng phụ không mấy tốt đẹp của macro.

Bạn có thể chuyển đổi nó thành macro ngắn bằng cách sử dụng biến tĩnh hoặc biến toàn cục, như sau:

static int s_endianess = 0;
#define ENDIANESS() ((s_endianess = 1), (*(unsigned char*) &s_endianess) == 0)

tôi nghĩ cái này là tốt nhất vì nó là cái đơn giản nhất. tuy nhiên nó không kiểm tra chống endian hỗn hợp
Hayri Uğur Koltuk

1
Tại sao không s_endianessđược đặt thành 1 để bắt đầu?
SquareRootOfTwentyTh Three

5

Mặc dù không có #define di động hoặc thứ gì đó để dựa vào, các nền tảng cung cấp các chức năng tiêu chuẩn để chuyển đổi đến và từ endian 'máy chủ' của bạn.

Nói chung, bạn thực hiện việc lưu trữ - vào đĩa hoặc mạng - bằng cách sử dụng 'network endian', là endian LỚN và tính toán cục bộ bằng host endian (trên x86 là LITTLE endian). Bạn sử dụng htons()ntohs()và bạn bè để chuyển đổi giữa hai.


4
#include <stdint.h>
#define IS_LITTLE_ENDIAN (*(uint16_t*)"\0\1">>8)
#define IS_BIG_ENDIAN (*(uint16_t*)"\1\0">>8)

6
Điều này cũng tạo ra mã thực thi, không phải là một hằng số. Bạn không thể làm "#if IS_BIG_ENDIAN"
Edward Falk

Tôi thích giải pháp này vì nó không dựa trên hành vi không xác định của tiêu chuẩn C / C ++, theo như tôi hiểu. Đó không phải là thời gian biên dịch nhưng giải pháp tiêu chuẩn duy nhất cho điều đó đang đợi c ++ 20 std :: endian
ceztko

4

Đừng quên rằng độ bền không phải là toàn bộ câu chuyện - kích thước của charcó thể không phải là 8 bit (ví dụ: DSP), phủ định bổ sung của hai không được đảm bảo (ví dụ: Cray), có thể cần phải căn chỉnh chặt chẽ (ví dụ: SPARC, ARM cũng đưa vào giữa -endian khi không có dấu), v.v., v.v.

Thay vào đó, có thể nhắm mục tiêu một kiến trúc CPU cụ thể .

Ví dụ:

#if defined(__i386__) || defined(_M_IX86) || defined(_M_IX64)
  #define USE_LITTLE_ENDIAN_IMPL
#endif

void my_func()
{
#ifdef USE_LITTLE_ENDIAN_IMPL
  // Intel x86-optimized, LE implementation
#else
  // slow but safe implementation
#endif
}

Lưu ý rằng giải pháp này cũng không phải là siêu di động, vì nó phụ thuộc vào các định nghĩa dành riêng cho trình biên dịch (không có tiêu chuẩn nào, nhưng đây là một bản tổng hợp các định nghĩa như vậy rất hay).


3

Thử cái này:

#include<stdio.h>        
int x=1;
#define TEST (*(char*)&(x)==1)?printf("little endian"):printf("Big endian")
int main()
{

   TEST;
}

2

Xin lưu ý rằng hầu hết các câu trả lời ở đây không phải là di động, vì các trình biên dịch ngày nay sẽ đánh giá các câu trả lời đó trong thời gian biên dịch (phụ thuộc vào việc tối ưu hóa) và trả về một giá trị cụ thể dựa trên một giá trị cụ thể, trong khi giá trị cụ thể của máy thực tế có thể khác. Các giá trị mà độ bền được kiểm tra sẽ không bao giờ đến được bộ nhớ hệ thống, do đó mã được thực thi thực sự sẽ trả về cùng một kết quả bất kể độ bền thực tế là bao nhiêu.

dụ , trong ARM Cortex-M3, độ bền được triển khai sẽ phản ánh trong một bit trạng thái AIRCR.ENDIANNESS và trình biên dịch không thể biết giá trị này trong thời gian biên dịch.

Kết quả tổng hợp cho một số câu trả lời được đề xuất ở đây:

https://godbolt.org/z/GJGNE2 cho câu trả lời này ,

https://godbolt.org/z/Yv-pyJ cho điều này câu trả lời , v.v.

Để giải quyết nó, bạn sẽ cần sử dụng volatilevòng loại. Yogeesh H T's Câu trả lời là một trong những gần gũi nhất để sử dụng thực tế đời sống hiện nay, nhưng kể từ Christophgợi ý giải pháp toàn diện hơn, một sửa chữa nhỏ để mình câu trả lời sẽ làm cho câu trả lời đầy đủ, chỉ cần thêm volatilevào union: static const volatile union.

Điều này sẽ đảm bảo việc lưu trữ và đọc từ bộ nhớ, điều này cần thiết để xác định độ bền.


2

Nếu bạn kết xuất bộ tiền xử lý #defines

gcc -dM -E - < /dev/null
g++ -dM -E -x c++ - < /dev/null

Bạn thường có thể tìm thấy những thứ có ích cho mình. Với logic thời gian biên dịch.

#define __LITTLE_ENDIAN__ 1
#define __BYTE_ORDER__ __ORDER_LITTLE_ENDIAN__

Tuy nhiên, nhiều trình biên dịch khác nhau có thể có các định nghĩa khác nhau.


0

Câu trả lời của tôi không phải như được hỏi nhưng nó thực sự đơn giản để tìm xem hệ thống của bạn là endian nhỏ hay endian lớn?

Mã:

#include<stdio.h>

int main()
{
  int a = 1;
  char *b;

  b = (char *)&a;
  if (*b)
    printf("Little Endian\n");
  else
    printf("Big Endian\n");
}

0

C Mã để kiểm tra xem một hệ thống là kiểu Ấn Độ nhỏ hay Ấn Độ lớn.

int i = 7;
char* pc = (char*)(&i);
if (pc[0] == '\x7') // aliasing through char is ok
    puts("This system is little-endian");
else
    puts("This system is big-endian");

-3

Macro để tìm endiannes

#define ENDIANNES() ((1 && 1 == 0) ? printf("Big-Endian"):printf("Little-Endian"))

hoặc là

#include <stdio.h>

#define ENDIAN() { \
volatile unsigned long ul = 1;\
volatile unsigned char *p;\
p = (volatile unsigned char *)&ul;\
if (*p == 1)\
puts("Little endian.");\
else if (*(p+(sizeof(unsigned long)-1)) == 1)\
puts("Big endian.");\
else puts("Unknown endian.");\
}

int main(void) 
{
       ENDIAN();
       return 0;
}

3
Macro đầu tiên không chính xác và sẽ luôn trả về "Big-Endian". Sự dịch chuyển bit không bị ảnh hưởng bởi độ bền - độ cuối chỉ ảnh hưởng đến việc đọc và lưu trữ vào bộ nhớ.
GaspardP
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.