【问题标题】:C, Little and Big Endian confusionC、Little Endian 和 Big Endian 混淆
【发布时间】:2019-06-30 01:51:59
【问题描述】:

我试图理解 C 编程内存字节顺序,但我很困惑。 我在这个网站上尝试了我的应用程序以进行输出验证:www.yolinux.com/TUTORIALS/Endian-Byte-Order.html

对于我在 C 程序中使用的 64 位值:

volatile long long ll = (long long)1099511892096;
__mingw_printf("\tlong long, %u Bytes, %u bits,\t%lld to %lli, %lli, 0x%016llX\n", sizeof(long long), sizeof(long long)*8, LLONG_MIN, LLONG_MAX , ll, ll);

void printBits(size_t const size, void const * const ptr)
{
    unsigned char *b = (unsigned char*) ptr;
    unsigned char byte;
    int i, j;
    printf("\t");
    for (i=size-1;i>=0;i--)
    {
        for (j=7;j>=0;j--)
        {
            byte = b[i] & (1<<j);
            byte >>= j;
            printf("%u", byte);
        }

        printf(" ");
    }
    puts("");
}

出局

long long,                8 Bytes,   64 bits,   -9223372036854775808 to 9223372036854775807, 1099511892096, 0x0000010000040880
80 08 04 00 00 01 00 00  (Little-Endian)
10000000 00001000 00000100 00000000 00000000 00000001 00000000 00000000
00 00 01 00 00 04 08 80  (Big-Endian)
00000000 00000000 00000001 00000000 00000000 00000100 00001000 10000000

测试

0x8008040000010000, 1000000000001000000001000000000000000000000000010000000000000000 // online website hex2bin conv. 
                    1000000000001000000001000000000000000000000000010000000000000000 // my C app
0x8008040000010000, 1000010000001000000001000000000000000100000000010000000000000000 // yolinux.com


0x0000010000040880, 0000000000000000000000010000000000000000000001000000100010000000      //online website hex2bin conv., 1099511892096  ! OK
                    0000000000000000000000010000000000000000000001000000100010000000      // my C app,  1099511892096 ! OK
[Convert]::ToInt64("0000000000000000000000010000000000000000000001000000100010000000", 2) // using powershell for other verif., 1099511892096 ! OK          
0x0000010000040880, 0000000000000000000000010000010000000000000001000000100010000100      // yolinux.com, 1116691761284 (from powershell bin conv.) ! BAD !

问题

yolinux.com 网站宣布 0x0000010000040880BIG ENDIAN !但我认为我的电脑使用 LITTLE ENDIAN (Intel proc.) 我从我的 C 应用程序和另一个网站 hex2bin 转换器获得相同的值 0x0000010000040880。 __mingw_printf(...0x%016llX...,...ll) 也打印 0x0000010000040880 如您所见。

在 yolinux 网站之后,我暂时在输出中反转了“(Little-Endian)”和“(Big-Endian)”标签。

此外,正数的符号位必须为 0,这在我的结果中也是如此,在 yolinux 结果中也是如此。(不能帮助我确定。)

如果我正确理解字节顺序,则只交换字节而不交换位,并且我的位组似乎正确反转。

这只是 yolinux.com 上的一个错误,还是我错过了关于 64 位数字和 C 编程的步骤?

【问题讨论】:

  • 当您使用printf 打印一个数字时,系统是大端还是小端并不重要——它们将打印相同的东西。大/小端仅与字节顺序在内存中的关系有关
  • 数字的十六进制表示不受内存中字节表示的影响,只有后者受字节序的影响。
  • @4386427,是的,但我通过函数和内存指针获得了值。这可以改变什么?我添加了函数。
  • @Aborted-Security 不,只有看内存顺序才能看到区别。
  • @Aborted-Security 人类代表 = Big Endian。如果需要,只需检查是否为小端,然后向后打印字节,否则将它们打印在内存中。顺便说一句,我看了那个教程,它很糟糕。使用char 类型、0x000000000000FF00 之类的字面量并使用位字段来证明字节顺序的人显然不知道他们在做什么,也不必移植代码。

标签: c endianness


【解决方案1】:

当您使用printf(和正确的格式说明符)打印一些“多字节”整数时,系统是小端还是大端都没有关系。结果是一样的。

little endian 和 big endian 的区别在于多字节类型在内存中的存储顺序。但是一旦数据从内存读入核心处理器,就没有区别了。

这段代码显示了如何将一个整数(4 个字节)放入我机器的内存中。

#include <stdio.h>

int main()
{
    unsigned int u = 0x12345678;
    printf("size of int is %zu\n", sizeof u);
    printf("DEC: u=%u\n", u);
    printf("HEX: u=0x%x\n", u);
    printf("memory order:\n");
    unsigned char * p = (unsigned char *)&u;
    for(int i=0; i < sizeof u; ++i) printf("address %p holds %x\n", (void*)&p[i], p[i]);
    return 0;
}

输出:

size of int is 4
DEC: u=305419896
HEX: u=0x12345678
memory order:
address 0x7ffddf2c263c holds 78
address 0x7ffddf2c263d holds 56
address 0x7ffddf2c263e holds 34
address 0x7ffddf2c263f holds 12

所以我可以看到我在一个小端机器上,因为 LSB(最低有效字节,即 78)存储在最低地址上。

在大端机器上执行相同的程序会(假设地址相同)显示:

size of int is 4
DEC: u=305419896
HEX: u=0x12345678
memory order:
address 0x7ffddf2c263c holds 12 
address 0x7ffddf2c263d holds 34 
address 0x7ffddf2c263e holds 56 
address 0x7ffddf2c263f holds 78 

现在是存储在最低地址的 MSB(最高有效字节,即 12)。

要了解的重要一点是,这与“多字节类型如何存储在内存中”有关。一旦整数从内存中读取到内核内部的寄存器中,该寄存器将在 小端和大端机器上以 0x12345678 的形式保存整数。

【讨论】:

  • 好的,你的代码我看得很清楚:) 非常感谢你的时间。但是,对不起,我坚持。问题是你在小和大的十六进制中做得很好。两个不同的值顺序(眼睛和内存),所以yolinux网站错了?他将 Endian 类型反转为 64 位数字?
  • @Aborted-Security 有时您想在软件中的字节序之间进行交换。一个示例是从网络接口读取数据时。在网络接口上,数据始终以 MSB 优先(网络字节顺序)发送。因此,如果您从 NIC 接收到表示整数的 4 个字节,并且您的系统是 little endian,那么您不能只按照接收到的顺序将 4 个字节写入内存。您需要先“交换”字节。
  • 非常感谢您的解释。我会考虑所有这些。
  • 那么,yolinux用不好的方法来解释Big和Little? 0x0000010000040880 != 0x8008040000010000。 ?重视记忆中的订单变化,我想要这个订单的六人代表......(我认为)
  • @Aborted-Security 好吧,当写成0x00000100000408800x8008040000010000 时,你肯定处理两个不同的数字 - 不管字节顺序如何,因为它无关与字节序。但是,如果您说:memory 包含以下十六进制值:00 00 01 00 00 04 08 80,那么小端和大端的实际数字是不同的。在小端上,实际数字是:0x8008040000010000 但在大端上,实际数字是:0x0000010000040880
【解决方案2】:

只有一种方法可以用十进制、二进制或十六进制格式表示整数。例如,数字43981 等于0xABCD,当写为十六进制时,或0b1010101111001101 写为二进制。任何其他值(0xCDAB0xDCBA 或类似值)代表不同的数字。

就 C 标准而言,您的编译器和 cpu 选择在内部存储此值的方式无关紧要;如果您特别不走运,则该值可以存储为36-bit one's complement,只要该标准规定的所有操作都具有相同的效果。

您在编程时很少需要检查您的内部数据表示。实际上,您关心字节序的唯一时间是在处理通信协议时,因为必须精确定义数据的二进制格式,但即便如此,无论架构如何,您的代码都不会有所不同:

// input value is big endian, this is defined
// by the communication protocol

uint32_t parse_comm_value(const char * ptr)
{
     // but bit shifts in C have the same
     // meaning regardless of the endianness
     // of your architecture

     uint32_t result = 0;
     result |= (*ptr++) << 24;
     result |= (*ptr++) << 16;
     result |= (*ptr++) << 8;
     result |= (*ptr++);
     return result;
}

Tl;dr 调用像printf("0x%llx", number); 这样的标准函数总是使用指定的格式打印正确的值。通过读取单个字节来检查内存内容可以为您提供架构中数据的表示形式。

【讨论】:

    猜你喜欢
    • 2018-05-15
    • 2020-12-01
    • 2022-06-10
    • 2012-10-09
    • 1970-01-01
    • 2010-10-16
    • 1970-01-01
    • 2019-03-13
    • 2022-12-12
    相关资源
    最近更新 更多