char 数组是如何存储的?
How was an array of char stored?
这是我发现的一些奇怪的东西:
当我有一个包含三个元素的 char* s,并将其赋值为 "21" 时,
s 打印出来的 short int 值好像是 12594,相当于二进制的 0010001 0010010,单独的 char 是 49 50。但是根据ASCII码表,'2'的值是50,'1'是49.
当我向右移动字符时,*(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 的顺序)。
您的系统恰好是小端系统。
这是我发现的一些奇怪的东西:
当我有一个包含三个元素的 char* s,并将其赋值为 "21" 时,
s 打印出来的 short int 值好像是 12594,相当于二进制的 0010001 0010010,单独的 char 是 49 50。但是根据ASCII码表,'2'的值是50,'1'是49.
当我向右移动字符时,
*(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 的顺序)。
您的系统恰好是小端系统。