char 数组是如何存储的?

How was an array of char stored?

这是我发现的一些奇怪的东西:

当我有一个包含三个元素的 char* s,并将其赋值为 "21" 时,

  1. s 打印出来的 short int 值好像是 12594,相当于二进制的 0010001 0010010,单独的 char 是 49 50。但是根据ASCII码表,'2'的值是50,'1'是49.

  2. 当我向右移动字符时,*(short*)s >>= 8,结果与(1.)一致,即'1'或49。但是在我分配字符后*s = '1',打印出来的s字符串也好像是"1",我之前以为会变成"11".

我现在对如何将位存储在 char 中感到困惑,希望有人能解释一下。

以下是我使用的代码:

#include <stdio.h>
#include <stdlib.h>

int main(void) {
  printf("%lu,%lu\n",sizeof(char), sizeof(short));
  char* s = malloc(sizeof(char)*3);
  *s = '2', *(s+1) = '1', *(s+2) = '[=10=]';
  printf("%s\n",s);
  printf("%d\n",*(short int*)s);
  *(short*)s >>= 8;
  printf("%s\n",s);
  printf("%d\n",*(short int*)s);
  *s = '1';
  printf("%s\n",s);
  return 0;
}

输出为:

1,2
21
12594
1
49
1

这个程序是在 macOS 上用 gcc 编译的。

这里需要对"endianess"的概念有所了解,即值可以表示为"little endian"和"big endian"。

我将跳过关于它有多合法以及涉及的未定义行为的讨论。
(不过,这里有一个相关的 link,由 Lundin 提供,致谢:
What is the strict aliasing rule?)

但是让我们看一下内存中的一对字节,其中低地址包含一个 50,高地址包含一个 49:

50 49

您通过显式设置低字节和高字节(通过 char 类型)以这种方式引入它们。

然后您阅读它们,迫使编译器将其视为 short,这是您系统上的两个字节大小的类型。

编译器和硬件可以用不同的 "opinions" 创建,以什么是两个字节值在两个连续字节中的良好表示。它被称为"endianess"。

两个完全符合标准的编译器可以像这样工作:

要返回的short

  • 取低地址的值,乘以256,加上高地址的值
  • 取高地址的值,乘以256,加上低地址的值

他们实际上并没有这样做,它是一种在硬件中实现的更有效的机制,但关键是即使在硬件中隐式实现也会做这个或那个。

您正在以一种标准不允许的方式通过别名类型来重新解释表示:您可以像处理 char 数组一样处理短值,但不能相反。这样做可能会导致优化编译器出现奇怪的错误,这些错误可能会假设该值从未被初始化,或者可能会优化掉包含未定义行为的完整代码分支。

那么你的问题的答案就叫做字节顺序。在大端表示中,最高有效字节具有最低地址(258 或 0x102 将按顺序表示为 2 字节 0x01、0x02),而在小端表示中,最低有效字节具有最低地址(0x102 表示为0x02、0x01 的顺序)。

您的系统恰好是小端系统。