首页 > 编程语言 >C/C++ 判断计算机存储器字节序(端序)的几种方式

C/C++ 判断计算机存储器字节序(端序)的几种方式

时间:2024-10-03 23:01:06浏览次数:8  
标签:端序 __ 字节 int C++ 编译器 printf ORDER

字节序分为存储器字节序和网络字节序(通常采用大端),这里主要讨论的是主存储器字节序。

主存是存储器中的一种,为什么只讨论主存?因为编写运行在现代主流操作系统上的程序,是没有 I/O 权限的。

主存字节序

所谓字节序就是字节排列的顺序,拿主存来说就是如果低字节存放在低地址处,就是低端字节序(小端),反之为高端字节序(大端)。拿 0x1234567 来说:

判断字节序

  1. 通过指针

既然字节序就是字节的排列顺序,那么我们把至少 2 字节的字节序列存放到主存中,如果能获取该数据的最低或最高地址的 1 字节数据,不就知道字节序了吗?对应 C/C++ 来说就是指针。

#include <stdio.h>

int main(void)
{
    unsigned int i = 1;
    char *ch_ptr = (char*)&i;  // 创建一个指向 i 的字符(字节)指针

    if (*ch_ptr)
    {
        printf("Little-Endian\n");  // 如果 *ch_ptr 为 1,表示最低位字节为 1,为小端
    }
    else
    {
        printf("Big-Endian\n");     // 否则为大端
    }

    return 0;
}

为了保障可移植性,我这里用的是 unsigned int i,以确保每个平台都至少 2 个字节。其它数据类型甚至是数组都是可以的,没有本质的区别,都是在主存中存储相应数量的字节序列。

  1. 使用联合体(Union)

联合体允许在相同的内存位置存储不同的数据类型,并且可以通过不同的成员来检视同一块内存区域。利用该特性,我们可以在联合体中定义两个成员,其中一个成员确保为 1 字节(当然,也可以定义成数组,然后取数组中的第一个元素),而另一个成员则确保每个平台都至少为 2 字节。

#include <stdio.h>

typedef union
{
	unsigned int i;
	char byte;
} ByteOrder;

int main(void)
{
    ByteOrder order;
    order.i = 1;  // 将整数 1 存储到联合体中

    if (order.byte)
	{
        printf("Little-Endian\n");  // 如果最低位字节存储的是 1,则为小端
    }
	else
	{
        printf("Big-Endian\n");     // 否则为大端
    }

    return 0;
}

如前所述,char byte 可以改为 char ch_arr[sizeof(unsigned int)] 形式的数组,然后需要将 order.byte 改为 order.ch_arr[0]

这两种方法本质上是一样的,都是通过判断数据的低位字节在内存中的位置来判断字节序。可以根据实际情况选择其中一种方法来使用。

除了使用联合体和指针外,还有一些其它方法可以检测字节序。

  1. 位移和掩码

这种方法利用位操作(位移和掩码)来检测字节序。它不依赖于联合体,也不需要指针操作,而是直接通过数值操作来判断:

#include <stdio.h>

int main(void)
{
	unsigned int i = 1; // 只有最低位是 1 的整数

    if ( (i >> 0) & 1 ) // 将 i 右移 0 位后与 1 进行与操作
	{
        printf("Little-Endian\n");
    }
	else
	{
        printf("Big-Endian\n");
    }

    return 0;
}

这种方法简单明了,通过将整数 1(其二进制形式在小端中为 01 00 00 00,在大端中为 00 00 00 01)的最低位(最右边的位)与 1 进行与操作。如果结果为 1,那么说明机器是小端字节序。

这种方法的好处是代码简单,且没有使用额外的内存(如联合体或指针)。它直接通过整型数值本身的操作来确定字节序。

性能对比

  1. 联合体:这种方法涉及访问联合体的不同成员。联合体方法的优点是直观易懂,但访问联合体成员可能导致微小的性能开销,尤其是在编译器优化不足的情况下。

  2. 指针:这种方法涉及将一个整数的地址转换为字符指针,然后检查具体的字节。这种方法可能稍微快一点,因为它直接操作内存,没有额外的抽象层。然而,这通常是微不足道的。

  3. 位移和掩码:此方法使用位操作来检查字节序。位操作效率非常高,因为它是直接在寄存器级别上进行的,没有内存访问的开销。

在大多数实际应用中,字节序的检查通常只在程序启动或初始化阶段进行一次,因此这里的性能差异几乎可以忽略不计。即便如此,从纯粹理论和微优化的角度来看,位移和掩码方法可能是最快的,因为它避免了任何内存访问,直接在处理器中完成所有操作。

然而,选择哪种方法应该基于代码的可读性、可维护性以及平台兼容性,而不仅仅是微小的性能差异。在大多数情况下,清晰和正确的代码要比微小的性能提升更加重要。

其它方法

除了前面提到的 3 种常见方法,还可以使用一些更具体或高级的技术来检测或处理字节序问题,尤其是在涉及到跨平台兼容性或网络通信时。

标准库函数

在某些编程环境中,标准库提供了函数来处理字节序问题。例如,在 C 语言中,网络编程常用的库如 <arpa/inet.h> 提供了 htonl()ntohl() 函数,用于将主机字节序转换为网络字节序,或反之。这些函数自动考虑了底层平台的字节序:

#include <stdio.h>
#include <arpa/inet.h>

int main(void)
{
    unsigned int x = 0x12345678;
    unsigned int y = htonl(x);  // 主机到网络字节序

    if (y == x)
	{
        printf("Big-Endian\n");
    }
	else
	{
        printf("Little-Endian\n");
    }

    return 0;
}

该方法不仅能判断字节序,还能在需要的时候转换字节序,非常适合网络通信中的数据交换。

编译器特定的预定义宏

一些编译器提供预定义的宏来指示目标平台的字节序。例如,GCC 和一些其他编译器可能定义了特定的宏,可以在编译时判断字节序。这种方法在编译时就确定了字节序,无需运行时检测。

  1. GCC 和 Clang 编译器

GCC 和 Clang 通常不直接提供检测字节序的宏,但你可以根据平台或者架构特定的预定义宏来推断字节序。例如,你可以检查是否定义了特定于某个架构的宏:

#include <stdio.h>

int main(void)
{
#if defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
    printf("Little-endian\n");
#elif defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
    printf("Big-endian\n");
#else
    printf("Unknown byte order\n");
#endif

	return 0;
}

这里使用了 GCC 和 Clang 编译器提供的 __BYTE_ORDER__ 宏以及相关的 __ORDER_LITTLE_ENDIAN____ORDER_BIG_ENDIAN__ 宏来确定字节序。

  1. MSVC 编译器

MSVC 编译器没有直接提供检测字节序的宏,因为 Windows 平台通常是小端字节序。如果你在使用 Visual Studio 且需要编写可移植的代码,可能需要自行定义这些宏或者使用其他方法来确定字节序。

  1. 跨平台编译

如果项目涉及不同的编译器和平台,就需要组合多种方法来使用,确保在这些宏未定义的情况下中也能够处理。

#include <stdio.h>

int main(void)
{
#if defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
    printf("Little-endian\n");
#elif defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
    printf("Big-endian\n");
#elif defined(_BIG_ENDIAN)
    printf("Big-endian\n");
#elif defined(_LITTLE_ENDIAN)
    printf("Little-endian\n");
#else
    printf("Byte order unknown or assuming default (e.g., little-endian)\n");
#endif

	return 0;
}

常见 CPU 的字节序

  1. 大端字节序:IBM、Sun、PowerPC。
  2. 小端字节序:x86、DEC 。

ARM 体系的 CPU 则大小端字节序通吃,具体用哪类字节序由硬件选择。

标签:端序,__,字节,int,C++,编译器,printf,ORDER
From: https://www.cnblogs.com/escwqa/p/18446114

相关文章

  • C++ 对C的扩展有哪些
    C++对C的扩展主要体现在以下几个方面:语法增强:变量声明位置更灵活:在C语言中,函数内的所有局部变量必须在函数开头的块内进行声明。而C++放松了这一限制,可以在函数内的任何位置声明变量,只要在使用该变量之前进行声明即可。例如:voidsomeFunction(){//C++中可以在循环内......
  • 字节输入流
    1.是什么        字节输入流(ByteInputStream)在Java中是用来读取原始字节流的数据。Java的java.io包提供了多种字节输入流类,其中InputStream是所有字节输入流类的超类。以下是关于字节输入流的详细解释和举例:字节输入流的概念:字节输入流用于读取二进制数据,比如图片......
  • C++之operator(附加返回引用的一些内容)
    在C++中,操作符重载允许你为自定义类型定义操作符的行为,使其可以像内置类型一样使用。通过定义operator函数,你可以指定操作符如何处理对象。基本语法ReturnTypeoperatorOp(Parameters){//实现}示例加法操作符重载#include<iostream>classPoint{public......
  • C++ 容器适配器
    除了顺序容器外,标准库还定义了三个顺序容器适配器:stack、queue和priority_queue。适配器(adaptor)是标准库中的一个通用概念****。容器、迭代器和函数都有适配器。本质上,一个适配器是一种机制,能使某种事物的行为看起来像另外一种事物一样。一个容器适配器接受一种已有的容器类型,......
  • C++ 额外的 string 操作
    string构造:▲《C++Primer》P321string裁剪:▲322修改string的操作:▲《C++Primer》P323string的搜索操作:▲《C++Primer》P325string的compare函数:▲《C++Primer》P327......
  • C++数组衰变机制
    inta[10]={};//下面两个式子等价int*p=a;int*p=&a[0];我们在讨论数组的时候经常看到这么一种说法,也就是说,数组名就是指向数组首元素的指针。但是上面这个过程产生了隐式转换,也就是数组衰变过程数组名!=指针数组就是数组,指针就是指针,不能将数组变量名认为是......
  • C++之size_t
    size_t是C++中的一个无符号整型,用于表示对象的大小或容器中的元素数量。它定义在<cstddef>头文件或<cstdlib>头文件中,通常用于数组索引和内存分配。其大小取决于平台(通常是32位或64位),因此在处理大数据时比int更安全。特点无符号类型:size_t只能表示非负数,适合表示......
  • C++之template
    C++模板是一种强大的特性,允许你编写通用的代码。它分为两种类型:函数模板和类模板。函数模板:可以定义一个函数,使用类型参数。例如:点击查看代码template<typenameT>Tadd(Ta,Tb){returna+b;}类模板:允许你定义一个类,使用类型参数。例如:点击查看代......
  • 南沙C++信奥赛陈老师解一本通题 2099:【23CSPJ普及组】公路(road)
    ​ 2099:【23CSPJ普及组】公路(road)时间限制:1000ms      内存限制:524288KB提交数:3793   通过数: 1575【题目描述】小苞准备开着车沿着公路自驾。公路上一共有 nn 个站点,编号为从 11 到nn。其中站点 ii 与站点i+1i+1 的距离为vivi 公里。......
  • 全网最适合入门的面向对象编程教程:55 Python字符串与序列化-字节序列类型和可变字节字
    全网最适合入门的面向对象编程教程:55Python字符串与序列化-字节序列类型和可变字节字符串摘要:在Python中,字符编码是将字符映射为字节的过程,而字节序列(bytes)则是存储这些字节的实际数据结构,字节序列和可变字节字符串的主要区别在于其可变性和用途,bytearray是可变的字节序列......