การตรวจจับ endianness โดยทางโปรแกรมในโปรแกรม C ++


211

มีวิธีการเขียนโปรแกรมเพื่อตรวจสอบว่าคุณอยู่ในสถาปัตยกรรมขนาดใหญ่หรือเล็กหรือไม่? ฉันต้องสามารถเขียนโค้ดที่จะทำงานบนระบบ Intel หรือ PPC และใช้รหัสเดียวกันทุกประการ (เช่นไม่มีการคอมไพล์แบบมีเงื่อนไข)


4
เพื่อประโยชน์ของความสมบูรณ์นี่คือลิงก์ไปยังคำถามของคนอื่นเกี่ยวกับการพยายามวัดความ endianness (ณ เวลารวบรวม): stackoverflow.com/questions/280162/…
Faisal Vali

14
ทำไมไม่ลองหา endianness ในเวลารวบรวม? มันไม่สามารถเปลี่ยนแปลงได้ที่รันไทม์
ephemient

3
AFAIK ไม่มีวิธีที่เชื่อถือได้และเป็นสากลในการทำเช่นนั้น gcc.gnu.org/ml/gcc-help/2007-07/msg00342.html
48956

คำตอบ:


174

ฉันไม่ชอบวิธีการตามประเภทของ punning - มันมักจะถูกเตือนโดยคอมไพเลอร์ นั่นคือสิ่งที่สหภาพมีไว้เพื่อ!

bool is_big_endian(void)
{
    union {
        uint32_t i;
        char c[4];
    } bint = {0x01020304};

    return bint.c[0] == 1; 
}

หลักการนี้เทียบเท่ากับกรณีตัวพิมพ์ตามที่ผู้อื่นแนะนำ แต่ชัดเจนกว่าและตาม C99 นั้นรับประกันว่าถูกต้อง gcc ชอบสิ่งนี้มากกว่าเมื่อเทียบกับการส่งพอยน์เตอร์โดยตรง

นี่ยังดีกว่าการแก้ไข endianness ในเวลารวบรวม - สำหรับระบบปฏิบัติการที่รองรับหลายสถาปัตยกรรม (เช่นไบนารีไขมันบน Mac os x) ซึ่งจะทำงานได้ทั้ง ppc / i386 ในขณะที่มันง่ายที่จะทำสิ่งต่าง ๆ เป็นอย่างอื่น .


51
ฉันไม่แนะนำให้ตั้งชื่อตัวแปร "bint" :)
mkb

42
คุณแน่ใจหรือไม่ว่าสิ่งนี้มีความหมายชัดเจน ใน C ++ เพียงหนึ่งในสมาชิกของสหภาพสามารถใช้งานได้ในครั้งเดียว - คือคุณไม่สามารถกำหนดใช้หนึ่งชื่อสมาชิกและอ่านโดยใช้อื่น (ถึงแม้จะมีข้อยกเว้นสำหรับ structs ที่รองรับรูปแบบ)
Faisal Vali

27
@ Matt: ผมมองเข้าไปใน Google และ bint ดูเหมือนว่าจะมีความหมายในภาษาอังกฤษว่าฉันไม่ได้ตระหนักถึง :)
เดวิด Cournapeau

17
ฉันได้ทำการทดสอบนี้แล้วและในทั้ง gcc 4.0.1 และ gcc 4.4.1 ผลลัพธ์ของฟังก์ชั่นนี้สามารถกำหนดได้ในเวลารวบรวมและถือว่าเป็นค่าคงที่ ซึ่งหมายความว่าคอมไพเลอร์จะลดลงหากสาขาที่ขึ้นอยู่กับผลลัพธ์ของฟังก์ชันนี้ แต่เพียงผู้เดียวและจะไม่ถูกนำไปใช้บนแพลตฟอร์มที่เป็นปัญหา นี่น่าจะไม่เป็นจริงสำหรับการใช้งาน htonl จำนวนมาก
Omnifarious

6
โซลูชันนี้พกพาได้จริงหรือ เกิดอะไรขึ้นถ้าCHAR_BIT != 8?
zorgit

80

คุณสามารถทำได้โดยการตั้งค่าบิต int และการปิดบัง แต่วิธีที่ง่ายที่สุดคือการใช้ตัวแปลงไบต์เครือข่ายที่สร้างขึ้น (ตั้งแต่ลำดับไบต์เครือข่ายมักเป็นปลายทางขนาดใหญ่)

if ( htonl(47) == 47 ) {
  // Big endian
} else {
  // Little endian.
}

บิตซออาจจะเร็วกว่า แต่วิธีนี้ง่ายตรงไปตรงมาและเป็นไปไม่ได้ที่จะเลอะเทอะ


1
เครือข่ายการแปลง ops ยังสามารถใช้ในการแปลงทุกอย่างให้เป็น endian ใหญ่ดังนั้นการแก้ปัญหาอื่น ๆ ที่ Jay กำลังเผชิญอยู่
Brian

6
@sharptooth - slow เป็นคำที่เกี่ยวข้อง แต่ใช่ถ้าความเร็วเป็นปัญหาจริงๆให้ใช้เพียงครั้งเดียวในช่วงเริ่มต้นของโปรแกรมและตั้งค่าตัวแปรส่วนกลางด้วย endianness
Eric Petroelje

5
htonl มีปัญหาอื่น: ในบางแพลตฟอร์ม (windows?), มันไม่ได้อยู่ใน C runtime library ที่เหมาะสม, แต่ในเพิ่มเติม, ไลบรารีที่เกี่ยวข้องกับเครือข่าย (socket, etc ... ). นี่เป็นอุปสรรคสำหรับฟังก์ชั่นเดียวหากคุณไม่ต้องการใช้ห้องสมุด
David Cournapeau

7
โปรดทราบว่าบน Linux (gcc) htonl อาจมีการพับอย่างต่อเนื่อง ณ เวลารวบรวมดังนั้นการแสดงออกของแบบฟอร์มนี้จึงไม่มีค่าใช้จ่ายในการดำเนินการเลย (เช่นมันคงที่ที่พับได้ถึง 1 หรือ 0 จากนั้นการกำจัดรหัสตายลบ สาขาอื่น ๆ ของ if)
bdonlan

2
นอกจากนี้ใน x86 htonl สามารถ (และเป็นบน Linux / gcc) ดำเนินการอย่างมีประสิทธิภาพมากโดยใช้อินไลน์แอสเซมเบลอร์โดยเฉพาะอย่างยิ่งถ้าคุณกำหนดเป้าหมายไมโครสถาปัตยกรรมด้วยการสนับสนุนสำหรับการBSWAPดำเนินงาน
bdonlan

61

โปรดดูบทความนี้ :

นี่คือรหัสบางส่วนเพื่อพิจารณาประเภทของเครื่องของคุณ

int num = 1;
if(*(char *)&num == 1)
{
    printf("\nLittle-Endian\n");
}
else
{
    printf("Big-Endian\n");
}

25
โปรดจำไว้ว่ามันขึ้นอยู่กับความแข็งแรงและความยาวที่แตกต่างกันซึ่งมักจะเป็นกรณี แต่ไม่รับประกัน
David Thornley

10
ฉันทำงานกับระบบฝังตัวที่ int สั้นและ char มีขนาดเท่ากัน ... ฉันจำไม่ได้ว่า int ปกติขนาดนั้นเป็น 2 ไบต์หรือไม่
rmeador

2
ทำไมคำตอบนี้สวยมากคำตอบเดียวที่ไม่ทำให้ฉันคิดว่า "เพื่อนคุณ wtf คุณกำลังทำอะไร" ซึ่งเป็นกรณีของคำตอบส่วนใหญ่ที่นี่: o
hanshenrik

2
@Shillard int ต้องมีขนาดใหญ่อย่างน้อย แต่ไม่มีข้อกำหนดในมาตรฐานสำหรับถ่านที่ถูก จำกัด ให้น้อยลง! หากคุณได้ดูครอบครัว TI F280x คุณจะค้นพบว่า CHAR_BIT คือ 16 และ sizeof (int) == sizeof (ถ่าน) ในขณะที่ข้อ จำกัด ที่คุณกล่าวถึงจะถูกเก็บไว้อย่างดี ...
Aconcagua

5
ทำไมไม่ใช้ uint8_t และ uint16_t
Rodrigo

58

คุณสามารถใช้std::endianหากคุณมีสิทธิ์เข้าถึงคอมไพเลอร์ C ++ 20 เช่น GCC 8+ หรือ Clang 7+

หมายเหตุ: การstd::endianเริ่มต้นใน<type_traits>แต่ถูกย้ายไป<bit>ที่ 2019 ประชุมโคโลญ GCC 8 เสียงดังกราว 7, 8 และ 9 มีมันอยู่ใน<type_traits>ขณะที่ GCC 9+ และเสียงดังกราว 10+ <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
}

5
เนื่องจากทุกคนฉันสามารถเข้าถึง C ++ 17 และ 20 ร่าง / ข้อเสนอได้ แต่ ณ ตอนนี้คอมไพเลอร์ C ++ 20 จะมีอยู่จริงหรือไม่?
Xeverous

@ Xeverous ต้องมีการระบุขอบเขตเท่านั้นดังนั้นฉันสงสัยว่าผู้ขายส่วนใหญ่จะเพิ่มลงในการใช้ stdlib เนื่องจากเป็นการเปลี่ยนแปลงก่อนหน้านี้อย่างใดอย่างหนึ่ง
Pharap

@Xeverous GCC 8 เปิดตัวและสนับสนุน
Lyberta

จาก 30+ คำตอบสำหรับคำถามนี้ดูเหมือนจะเป็นคำตอบเดียวที่ถูกต้องสมบูรณ์ (ด้วยคำตอบอื่นที่ถูกต้องอย่างน้อยในบางส่วน)
IIsspectable

40

โดยปกติจะทำในเวลาคอมไพล์ (โดยเฉพาะสำหรับเหตุผลด้านประสิทธิภาพ) โดยใช้ไฟล์ส่วนหัวที่มีให้จากคอมไพเลอร์หรือสร้างของคุณเอง บน linux คุณมีไฟล์ส่วนหัว "/usr/include/endian.h"


8
ฉันไม่อยากเชื่อเลยว่าสิ่งนี้จะไม่ได้รับการโหวตให้สูงขึ้น ไม่ใช่ว่า endianness จะเปลี่ยนแปลงภายใต้โปรแกรมที่คอมไพล์ดังนั้นไม่จำเป็นต้องทำการทดสอบรันไทม์
Dolda2000

@ Dolda2000 มันเป็นไปได้ที่จะเห็นโหมด ARM endian
Tyzoid

10
@Tyzoid: ไม่โปรแกรมที่คอมไพล์จะทำงานภายใต้โหมด endian ที่ถูกคอมไพล์เสมอแม้ว่าโปรเซสเซอร์จะมีความสามารถเช่นกันก็ตาม
Dolda2000

16

ฉันประหลาดใจที่ไม่มีใครพูดถึงมาโครที่ตัวประมวลผลล่วงหน้ากำหนดโดยค่าเริ่มต้น ในขณะที่สิ่งเหล่านี้จะแตกต่างกันไปขึ้นอยู่กับแพลตฟอร์มของคุณ พวกมันสะอาดกว่าการเขียนเช็คเอนเดียนของคุณเอง

ตัวอย่างเช่น; หากเราดูมาโครในตัวที่ GCC กำหนด (บนเครื่อง X86-64):

:| gcc -dM -E -x c - |grep -i endian
#define __LITTLE_ENDIAN__ 1

บนเครื่อง PPC ฉันได้รับ:

:| gcc -dM -E -x c - |grep -i endian
#define __BIG_ENDIAN__ 1
#define _BIG_ENDIAN 1

( :| gcc -dM -E -x c -เวทย์มนตร์พิมพ์มาโครในตัวทั้งหมด)


7
มาโครเหล่านี้ไม่แสดงผลอย่างสม่ำเสมอ ตัวอย่างเช่นใน gcc 4.4.5 จาก Redhat 6 repo การรันecho "\n" | gcc -x c -E -dM - |& grep -i 'endian'จะไม่ส่งคืนอะไรในขณะที่ gcc 3.4.3 (จากเดิม/usr/sfw/bin) ใน Solaris มีคำจำกัดความตามบรรทัดเหล่านี้ ฉันเคยเห็นปัญหาที่คล้ายกันใน VxWorks Tornado (gcc 2.95) -vs- VxWorks Workbench (gcc 3.4.4)
Brian Vandenberg

15

อืม ... มันทำให้ฉันประหลาดใจที่ไม่มีใครรู้ว่าคอมไพเลอร์จะทำการทดสอบให้เหมาะสมและจะให้ผลลัพธ์ที่แน่นอนเป็นค่าตอบแทน นี่ทำให้ตัวอย่างโค้ดทั้งหมดข้างต้นไร้ประโยชน์อย่างมีประสิทธิภาพ สิ่งเดียวที่จะกลับมาคือ endianness ที่รวบรวมเวลา! และใช่ฉันทดสอบตัวอย่างข้างต้นทั้งหมด นี่คือตัวอย่างของ MSVC 9.0 (Visual Studio 2008)

รหัสเพียว C

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;
}

ถอดชิ้นส่วน

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

อาจเป็นไปได้ที่จะปิดการเพิ่มประสิทธิภาพเวลารวบรวมใด ๆ สำหรับฟังก์ชั่นนี้ แต่ฉันไม่รู้ มิฉะนั้นอาจเป็นไปได้ที่จะฮาร์ดโค้ดไว้ในชุดแม้ว่าจะไม่สามารถพกพาได้ และแม้แต่นั้นก็อาจได้รับการปรับให้เหมาะสม มันทำให้ฉันคิดว่าฉันต้องการแอสเซมบลีเส็งเคร็งจริง ๆ ใช้รหัสเดียวกันสำหรับซีพียู / ชุดคำสั่งที่มีอยู่ทั้งหมดและดี .... ไม่เป็นไร

นอกจากนี้บางคนที่นี่บอกว่า endianness ไม่เปลี่ยนแปลงในระหว่างเวลาทำงาน ไม่ถูกต้อง. มีเครื่องจักร bi-endian อยู่ที่นั่น endianness ของพวกเขาอาจแตกต่างกันไปการประหารชีวิต นอกจากนี้ยังมีไม่เพียง Endian น้อยและ Big Endian แต่ยัง endiannesses อื่น ๆ (คำ)

ฉันเกลียดและรักการเขียนโปรแกรมในเวลาเดียวกัน ...


11
คุณไม่ต้องคอมไพล์ซ้ำเพื่อให้ทำงานบนแพลตฟอร์มที่แตกต่างกันอยู่ดี?
bobobobo

2
แม้ว่าจะใช้งานได้ดีกับ MSVC แต่ก็ไม่ได้มีไว้สำหรับ GCC ทุกรุ่นในทุกสถานการณ์ ดังนั้น "การตรวจสอบรันไทม์" ภายในลูปวิกฤติอาจถูกยกเลิกการแยกย่อยอย่างถูกต้องในเวลาคอมไพล์หรือไม่ ไม่มีการรับประกัน 100%
สีฟ้า

21
ไม่มีสิ่งเช่นโปรเซสเซอร์ x86 ที่มีขนาดใหญ่ แม้ว่าคุณจะเรียกใช้ Ubuntu บนโปรเซสเซอร์ biendian (เช่น ARM หรือ MIPS) ELF executables มักจะเป็นใหญ่ (MSB) หรือ endian (LSB) เล็กน้อย ไม่สามารถสร้างไฟล์ประมวลผล biendian ได้ดังนั้นจึงไม่จำเป็นต้องตรวจสอบรันไทม์
Fabel

4
หากต้องการปิดการปรับให้เหมาะสมในวิธีนี้ให้ใช้ 'volatile union ... ' มันบอกคอมไพเลอร์ว่า 'u' สามารถเปลี่ยนแปลงได้จากที่อื่นและควรโหลดข้อมูล
mishmashru

1
สำหรับฟังก์ชั่นนี้เพื่อส่งคืนค่าที่แตกต่างกันขณะรันไทม์กว่าเครื่องมือเพิ่มประสิทธิภาพกำลังคำนวณว่ามันจะบอกเป็นนัยว่าเครื่องมือเพิ่มประสิทธิภาพเริ่มต้นแล้ว คุณกำลังบอกว่ามีตัวอย่างของรหัสไบนารีที่ได้รับการปรับปรุงให้ดีที่สุดซึ่งอาจเรียกใช้บนสถาปัตยกรรมที่แตกต่างกันสองแห่งที่มีความแตกต่างกันแม้ว่าจะมีสมมติฐานที่ชัดเจนโดยเครื่องมือเพิ่มประสิทธิภาพ (ตลอดโปรแกรม) ในระหว่างการรวบรวมซึ่งดูเหมือนว่า สถาปัตยกรรม?
สกอตต์

13

ประกาศตัวแปร int:

int variable = 0xFF;

ตอนนี้ใช้ตัวชี้ถ่าน * เพื่อส่วนต่าง ๆ ของมันและตรวจสอบสิ่งที่อยู่ในส่วนเหล่านั้น

char* startPart = reinterpret_cast<char*>( &variable );
char* endPart = reinterpret_cast<char*>( &variable ) + sizeof( int ) - 1;

ในตอนนี้คุณสามารถตรวจจับ endianness ได้ขึ้นอยู่กับจุดใดจุดหนึ่งถึง 0xFF สิ่งนี้ต้องใช้ sizeof (int)> sizeof (char) แต่แน่นอนว่าเป็นจริงสำหรับแพลตฟอร์มที่กล่าวถึง


8

สำหรับรายละเอียดเพิ่มเติมคุณอาจต้องการตรวจสอบบทความCodeproject แนวคิดพื้นฐานเกี่ยวกับ Endianness :

วิธีการทดสอบ Endian type ณ รันไทม์แบบไดนามิก?

ตามที่อธิบายไว้ในคำถามที่พบบ่อยเกี่ยวกับภาพเคลื่อนไหวของคอมพิวเตอร์คุณสามารถใช้ฟังก์ชั่นต่อไปนี้เพื่อดูว่ารหัสของคุณกำลังทำงานอยู่ในระบบ Little- หรือ Big-Endian: ยุบ

#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);
}

รหัสนี้กำหนดค่า 0001h ให้กับจำนวนเต็ม 16 บิต ตัวชี้ถ่านจะถูกกำหนดให้ชี้ไปที่ไบต์แรก (สำคัญน้อยที่สุด) ของค่าจำนวนเต็ม หากไบต์แรกของจำนวนเต็มคือ 0x01h แสดงว่าระบบเป็น Little-Endian (0x01h นั้นอยู่ต่ำสุดหรือสำคัญน้อยที่สุด) หากเป็น 0x00h แสดงว่าระบบเป็น Big-Endian


6

วิธี C ++ คือการใช้บูสต์โดยที่การตรวจสอบพรีโพรเซสเซอร์และ casts ถูกแยกส่วนภายในไลบรารีที่ผ่านการทดสอบอย่างละเอียดมาก

Predef ห้องสมุด (เพิ่ม / predef.h) ตระหนักถึงสี่ชนิดที่แตกต่างกันของ endianness

ห้องสมุด Endianกำลังวางแผนที่จะส่งไปยัง C ++ มาตรฐานและการสนับสนุนที่หลากหลายของการดำเนินการกับข้อมูล endian ที่มีความอ่อนไหว

ตามที่ระบุไว้ในคำตอบข้างต้น Endianness จะเป็นส่วนหนึ่งของ c ++ 20


1
FYI ลิงก์ "สี่ประเภทต่าง ๆ ของ endianness" เสีย
Remy Lebeau

แก้ไขและทำวิกิ
fuzzyTew

5

ถ้าคุณไม่ได้ใช้เฟรมเวิร์กที่ต่อเข้ากับโพรเซสเซอร์ PPC และ Intel คุณจะต้องทำการคอมไพล์แบบมีเงื่อนไขเนื่องจากแพลตฟอร์ม PPC และ Intel นั้นมีสถาปัตยกรรมฮาร์ดแวร์แตกต่างกันไปอย่างสิ้นเชิงท่อส่งสัญญาณและอื่น ๆ ทั้งสอง.

สำหรับการค้นหา endianness ทำต่อไปนี้:

short temp = 0x1234;
char* tempChar = (char*)&temp;

คุณจะได้รับ tempChar เป็น 0x12 หรือ 0x34 ซึ่งคุณจะรู้ว่า endianness


3
สิ่งนี้ขึ้นอยู่กับความสั้นที่แน่นอนว่า 2 ไบต์ซึ่งไม่รับประกัน
sharptooth

3
มันจะเป็นการเดิมพันที่ปลอดภัยทีเดียวโดยอ้างอิงจากสถาปัตย์ทั้งสองที่ให้ไว้ในคำถาม
Daemin

8
รวมstdint.hและใช้int16_tเพื่อการพิสูจน์ในอนาคตต่อความแตกต่างในระยะสั้นบนแพลตฟอร์มอื่น
เดนิส Skidmore

4

ฉันจะทำสิ่งนี้:

bool isBigEndian() {
    static unsigned long x(1);
    static bool result(reinterpret_cast<unsigned char*>(&x)[0] == 0);
    return result;
}

ในบรรทัดเหล่านี้คุณจะได้รับฟังก์ชั่นเวลาที่มีประสิทธิภาพซึ่งทำการคำนวณเพียงครั้งเดียวเท่านั้น


คุณสามารถแทรกมันได้หรือไม่ ไม่แน่ใจว่าอินไลน์ทำให้เกิดบล็อกหน่วยความจำหลายตัวของตัวแปรแบบคงที่หรือไม่
aah134

4

ตามที่ระบุไว้ข้างต้นใช้กลวิธียูเนี่ยน

มีปัญหาเล็กน้อยกับปัญหาที่แนะนำไว้ข้างต้น แต่ที่น่าสังเกตว่าการเข้าถึงหน่วยความจำที่ไม่ได้จัดแนวนั้นช้ามากสำหรับสถาปัตยกรรมส่วนใหญ่และคอมไพเลอร์บางตัวจะไม่รู้จักภาคแสดงคงที่เช่นนั้นเว้นแต่จะจัดแนวคำ

เนื่องจากการทดสอบ endian นั้นน่าเบื่อเพียงแค่นี้ไปที่ฟังก์ชัน (เทมเพลต) ซึ่งจะพลิกอินพุต / เอาต์พุตของเลขจำนวนเต็มตามที่คุณต้องการโดยไม่คำนึงถึงสถาปัตยกรรมโฮสต์

#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;
};

การใช้งาน:

ในการแปลงจาก endian ที่กำหนดให้ใช้:

host = endian(source, endian_of_source)

หากต้องการแปลงจากโฮสต์ endian เป็น endian ที่กำหนดให้ใช้:

output = endian(hostsource, endian_you_want_to_output)

โค้ดผลลัพธ์นั้นเร็วเท่ากับการเขียนชุดประกอบมือบนเสียงดังกราวบน gcc มันช้ากว่าเล็กน้อย (ไม่มีการควบคุม &, <<, >>, | สำหรับทุกไบต์) แต่ก็ยังดี


4
bool isBigEndian()
{
    static const uint16_t m_endianCheck(0x00ff);
    return ( *((uint8_t*)&m_endianCheck) == 0x0); 
}

1
สิ่งนี้จะเทียบเท่าหรือไม่ #define IS_BIGENDIAN() (*((char*) &((int){ 0x00ff })) == (0x00))
Emanuel

4

อย่าใช้union!

C ++ ไม่อนุญาตให้พิมพ์ punning ผ่านunions!
การอ่านจากเขตข้อมูลร่วมที่ไม่ใช่เขตข้อมูลสุดท้ายที่เขียนถึงเป็นพฤติกรรมที่ไม่ได้กำหนด !
คอมไพเลอร์จำนวนมากสนับสนุนการทำเช่นนั้นเป็นส่วนขยาย แต่ภาษาไม่รับประกัน

ดูคำตอบนี้สำหรับรายละเอียดเพิ่มเติม:

https://stackoverflow.com/a/11996970


มีคำตอบที่ถูกต้องเพียงสองคำเท่านั้นเท่านั้น

คำตอบแรกถ้าคุณเข้าถึงระบบที่รองรับ C ++ 20
ให้ใช้std::endianจาก<type_traits>ส่วนหัว

(ในขณะที่เขียน, C ++ 20 ยังไม่ได้รับการปล่อยตัว แต่ถ้าบางสิ่งบางอย่างเกิดขึ้นกับstd::endianการรวมของสิ่งนี้จะเป็นวิธีที่ต้องการทดสอบ endianness ในเวลารวบรวมจาก C ++ 20 เป็นต้นไป)

C ++ 20 เป็นต้นไป

constexpr bool is_little_endian = (std::endian::native == std::endian::little);

ก่อนถึง C ++ 20 คำตอบที่ถูกต้องเพียงข้อเดียวคือเก็บจำนวนเต็มจากนั้นตรวจสอบไบต์แรกจนถึงประเภท punning
ซึ่งแตกต่างจากการใช้unions นี้ได้รับอนุญาตอย่างชัดแจ้งโดยระบบชนิดของ C ++

สิ่งสำคัญคือต้องจำไว้ว่าstatic_castควรใช้ความสะดวกในการพกพาที่เหมาะสม
เนื่องจากreinterpret_castมีการกำหนดการนำไปใช้งาน

หากโปรแกรมพยายามเข้าถึงค่าที่เก็บไว้ของวัตถุผ่าน glvalue นอกเหนือจากประเภทใดประเภทหนึ่งต่อไปนี้พฤติกรรมจะไม่ได้กำหนด: ... a charหรือunsigned chartype

C ++ 11 เป็นต้นไป

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;
}

C ++ 11 เป็นต้นไป (ไม่มี enum)

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);
}

C ++ 98 / C ++ 03

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);
}

3
union {
    int i;
    char c[sizeof(int)];
} x;
x.i = 1;
if(x.c[0] == 1)
    printf("little-endian\n");
else    printf("big-endian\n");

นี่คือทางออกอื่น คล้ายกับโซลูชันของ Andrew Hare


3

ยังไม่ทดลอง แต่ในความคิดของฉันมันควรจะใช้ได้ไหม ทำให้มันเป็น 0x01 สำหรับ endian น้อยและ 0x00 สำหรับ endian ใหญ่?

bool runtimeIsLittleEndian(void)
{
 volatile uint16_t i=1;
 return  ((uint8_t*)&i)[0]==0x01;//0x01=little, 0x00=big
}

3

ประกาศ:

โพสต์เริ่มต้นของฉันประกาศอย่างไม่ถูกต้องว่า "เวลารวบรวม" มันไม่เป็นไปไม่ได้แม้แต่ในมาตรฐาน C ++ ปัจจุบัน constexpr ไม่ได้หมายความว่าฟังก์ชันจะทำการคำนวณเวลาคอมไพล์เสมอ ขอบคุณ Richard Hodges สำหรับการแก้ไข

เวลาในการรวบรวม, ไม่ใช่มาโคร, โซลูชัน C ++ 11 constexpr:

union {
  uint16_t s;
  unsigned char c[2];
} constexpr static  d {1};

constexpr bool is_little_endian() {
  return d.c[0] == 1;
}

2
มีเหตุผลบางอย่างที่คุณใช้ char ที่ไม่ได้ลงชื่อเหนือ uint8_t หรือไม่?
เควิน

0 ค่าใช้จ่ายในการดำเนินการ ... ฉันชอบมัน!
hanshenrik

ฉันเดาว่ามันตรวจจับ endiannes ของเครื่องสร้างไม่ใช่เป้าหมายใช่ไหม
hutorny

2
ไม่ใช่ UB นี้ใน C ++ ใช่ไหม
rr-

6
สิ่งนี้ไม่ถูกกฎหมายในบริบทของ constexpr คุณไม่สามารถเข้าถึงสมาชิกของสหภาพที่ยังไม่ได้เริ่มต้นโดยตรง ไม่มีวิธีใดที่จะตรวจจับ endianness ที่ถูกต้องตามกฎหมายในเวลารวบรวมโดยไม่มีเวทมนต์ประมวลผลล่วงหน้า
Richard Hodges

2

คุณยังสามารถทำสิ่งนี้ผ่านตัวประมวลผลล่วงหน้าโดยใช้ไฟล์ส่วนหัวบูสต์ซึ่งพบได้ในบูสต์ endian


1

เว้นแต่ว่าส่วนหัวของ endian นั้นเป็น GCC เท่านั้น แต่จะให้มาโครที่คุณสามารถใช้ได้

#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__และ__ORDER_BIG_ENDIAN__?
Xeverous

1

หากคุณไม่ต้องการการรวบรวมตามเงื่อนไขคุณสามารถเขียนโค้ดอิสระของ endian ได้ นี่คือตัวอย่าง (นำมาจากRob Pike ):

การอ่านจำนวนเต็มเก็บไว้ใน little-endian บนดิสก์ในลักษณะอิสระ endian:

i = (data[0]<<0) | (data[1]<<8) | (data[2]<<16) | (data[3]<<24);

รหัสเดียวกันพยายามพิจารณา endianness ของเครื่อง:

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

ช่างเป็นความคิดที่ดี! และตอนนี้ลองโอนจำนวนเต็มของคุณผ่านซ็อกเก็ตเครือข่ายไปยังอุปกรณ์ที่ไม่รู้จัก
Maksym Ganenko

@MaksymGanenko ฉันไม่ได้รับความคิดเห็นของคุณ มันประชดเหรอ? ฉันไม่แนะนำให้ไม่ระบุ endianness ของข้อมูลต่อเนื่อง ฉันแนะนำไม่ให้เขียนรหัสขึ้นอยู่กับ endianness ของเครื่องที่รับข้อมูล
fjardon

@MaksymGanenko หากคุณลงคะแนนคุณสามารถอธิบายได้ว่าทำไมคำตอบนั้นไม่ถูกต้อง อย่างน้อยเพื่อช่วยให้ผู้อ่านที่มีศักยภาพเข้าใจว่าทำไมพวกเขาไม่ควรทำตามคำตอบของฉัน
fjardon


0

แล้วเรื่องนี้ล่ะ

#include <cstdio>

int main()
{
    unsigned int n = 1;
    char *p = 0;

    p = (char*)&n;
    if (*p == 1)
        std::printf("Little Endian\n");
    else 
        if (*(p + sizeof(int) - 1) == 1)
            std::printf("Big Endian\n");
        else
            std::printf("What the crap?\n");
    return 0;
}

0

นี่คือเวอร์ชั่น C อีกอัน มันกำหนดแมโครที่เรียกว่าwicked_cast()สำหรับประเภทอินไลน์เล่นสำนวนผ่าน C99 ตัวอักษรยูเนี่ยนและไม่ได้มาตรฐาน__typeof__ผู้ประกอบการ

#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);
}

ถ้าจำนวนเต็มเป็นค่าไบต์เดียวความไม่มีจุดมุ่งหมายและข้อผิดพลาดในการคอมไพล์เวลาจะถูกสร้างขึ้น


0

คอมไพเลอร์ทาง C (ที่ทุกคนอย่างน้อยฉันรู้) ทำงาน endianness มีจะต้องตัดสินใจที่รวบรวมเวลา แม้แต่โปรเซสเซอร์ biendian (เช่น ARM och MIPS) คุณต้องเลือก endianness ในเวลารวบรวม นอกจากนี้ endianness ถูกกำหนดในรูปแบบไฟล์ทั่วไปทั้งหมดสำหรับไฟล์เรียกทำงาน (เช่น ELF) แม้ว่ามันจะเป็นไปได้ที่จะสร้างไบนารีไบน์ของรหัส biandian (สำหรับเซิร์ฟเวอร์ ARM บางอย่างอาจจะใช้ประโยชน์จาก?) มันอาจจะต้องทำในการชุมนุม


-1

ดังที่ Coriiander ระบุไว้ส่วนใหญ่ (ถ้าไม่ใช่ทั้งหมด) ของรหัสเหล่านี้ที่นี่จะได้รับการปรับปรุงให้ดีที่สุดเมื่อถึงเวลารวบรวมดังนั้นไบนารีที่สร้างขึ้นจะไม่ตรวจสอบ "endianness" ในเวลาทำงาน

มันถูกตั้งข้อสังเกตว่าการปฏิบัติการที่กำหนดไม่ควรรันในคำสั่งไบต์ที่แตกต่างกันสองคำสั่ง แต่ฉันไม่รู้ว่ามันเป็นเช่นนั้นเสมอและดูเหมือนว่าแฮ็คจะให้ฉันตรวจสอบเวลารวบรวม ดังนั้นฉันเขียนฟังก์ชั่นนี้:

#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 ไม่สามารถปรับรหัสนี้ให้เหมาะสมแม้ว่าจะเพิ่มประสิทธิภาพรหัสอื่นที่นี่ ฉันเชื่อว่าเป็นเพราะฉันปล่อยให้ค่า "สุ่ม" ที่ได้รับการจัดสรรในหน่วยความจำไบต์ที่มีขนาดเล็กตามที่มันเป็น (อย่างน้อย 7 ของบิต) ดังนั้นคอมไพเลอร์ไม่สามารถรู้ว่าค่าสุ่มคืออะไรและไม่ปรับให้เหมาะสม ฟังก์ชั่นออกไป

ฉันได้เขียนโค้ดฟังก์ชันเพื่อให้การตรวจสอบทำได้เพียงครั้งเดียวและค่าส่งคืนจะถูกเก็บไว้สำหรับการทดสอบครั้งต่อไป


เหตุใดจึงจัดสรร 4 ไบต์เพื่อทำงานกับค่า 2 ไบต์ ทำไมต้องปกปิดค่าที่ไม่แน่นอนด้วย0x7FE? ทำไมต้องใช้งานmalloc()เลย? ที่สิ้นเปลือง และ_BEเป็นหน่วยความจำรั่ว (แม้ว่าเล็ก) และสภาพการแข่งขันที่รอให้เกิดขึ้นประโยชน์ของการแคชผลลัพธ์แบบไดนามิกไม่คุ้มกับปัญหา ฉันจะทำสิ่งนี้มากกว่านี้แทน: ใช้งาน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]); }ง่ายและมีประสิทธิภาพ
Remy Lebeau

@RemyLebeau จุดทั้งหมดของคำตอบของฉันคือการสร้างรหัสที่ไม่ได้รับการปรับแต่งโดยคอมไพเลอร์ แน่นอนว่าโค้ดของคุณนั้นง่ายกว่ามาก แต่เมื่อเปิดการปรับแต่งแล้วมันจะกลายเป็นบูลีนค่าคงที่หลังจากคอมไพล์แล้ว ตามที่ระบุไว้ในคำตอบของฉันฉันไม่รู้จริง ๆ ว่ามีวิธีการรวบรวมรหัส C ในลักษณะที่รันได้แบบเดียวกันบนคำสั่งไบต์ทั้งสองหรือไม่และฉันก็อยากรู้ด้วยว่าฉันสามารถตรวจสอบที่รันไทม์ แม้จะมีการปรับปรุงให้เหมาะสมที่สุด
Tex Killer

@TexKiller ทำไมไม่เพียงแค่ปิดการใช้งานการปรับให้เหมาะสมสำหรับรหัส? ใช้volatileหรือ#pragmaฯลฯ
Remy Lebeau

@RemyLebeau ฉันไม่รู้จักคำหลักเหล่านั้นในเวลานั้นและฉันเพิ่งใช้มันเป็นความท้าทายเล็กน้อยเพื่อป้องกันการเพิ่มประสิทธิภาพของคอมไพเลอร์ด้วยสิ่งที่ฉันรู้
Tex Killer

-1

ในขณะที่ไม่มีวิธีที่รวดเร็วและเป็นมาตรฐานในการพิจารณามันจะเอาท์พุทมัน:

#include <stdio.h> 
int main()  
{ 
   unsigned int i = 1; 
   char *c = (char*)&i; 
   if (*c)     
       printf("Little endian"); 
   else
       printf("Big endian"); 
   getchar(); 
   return 0; 
} 

-1

ดูEndianness - ภาพประกอบรหัส C-Level

// 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;
} 

-2

ฉันกำลังอ่านหนังสือ: ระบบคอมพิวเตอร์: มุมมองของโปรแกรมเมอร์และมีปัญหาในการพิจารณาว่า endian ตัวนี้คืออะไรโดยโปรแกรม C

ฉันใช้คุณสมบัติของตัวชี้เพื่อทำดังต่อไปนี้:

#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;
}

ในฐานะที่เป็นintใช้เวลา 4 ไบต์และถ่านใช้เวลาเพียง 1 ไบต์ เราสามารถใช้ตัวชี้ถ่านให้ชี้ไปที่intมีมูลค่า 1 ดังนั้นหากคอมพิวเตอร์ที่เป็น endian เล็ก ๆ น้อย ๆถ่านที่ตัวชี้ถ่านจุดที่จะต้องอยู่กับมูลค่า 1 มิฉะนั้นค่าที่ควรจะเป็น 0


สิ่งนี้จะได้รับการปรับปรุงโดยใช้ int32t
shuttle87

1
^ ถ้าคุณต้องการ nitpick ที่ดีที่สุดที่นี่คือ int16_fast_t และรหัสปัจจุบันของ @ Archimedes520 จะไม่ทำงานบนส่วนโค้งที่ int อยู่ในระดับ int8;) (ซึ่งอาจขัดกับมาตรฐาน c ในตอนแรก)
hanshenrik
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.