
🦆 个人主页:深邃-
目录
整数在内存中的存储
整数的二进制表示有原码、反码、补码三种形式,有符号整数的表示分为符号位(最高位,0 正 1 负)和数值位。
- 正整数:原、反、补码完全相同
- 负整数:原码直接翻译二进制,反码为原码符号位不变其余位取反,补码为反码 + 1
- 内存中实际存储的是整数的补码,原因是可统一处理符号位与数值域、统一加减法运算(CPU 只有加法器)、原补码转换无需额外硬件电路。
大小端字节序和字节序判断
当我们了解了整数在内存中存储后,我们调试看⼀个细节:
c
#include <stdio.h>
int main()
{
int a = 0x11223344;
return 0;
}

调试的时候,我们可以看到在a中的 0x11223344 这个数字是按照字节为单位,倒着存储的。这是为什么呢?
大小端概念
超过 1 个字节的数据在内存中存储存在顺序差异,分为两种模式:
- 大端(存储)模式:数据的低位字节内容保存在内存高地址处,高位字节内容保存在内存低地址处
- 小端(存储)模式:数据的低位字节内容保存在内存低地址处,高位字节内容保存在内存高地址处
大小端存在的原因
计算机以字节为单位分配地址,而 C 语言存在 16bit、32bit 等多字节类型,处理器寄存器宽度大于 1 字节时,多字节的存储安排方式不同,因此产生大小端模式。
- X86 结构为小端模式,KEIL C51 为大端模式
- 部分 ARM、DSP 为小端模式,部分 ARM 可通过硬件选择大小端
练习1
大小端判断(百度笔试题)
请简述大端字节序和小端字节序的概念,设计一个小程序来判断当前机器的字节序。(10分)- 百度笔
试题
c
#include <stdio.h>
int check_sys()
{
int a = 1;
if (*(char*)&a == 1)//int*
{
return 1;//小端
}
else
{
return 0;//大端
}
}
int main()
{
if(check_sys() == 1)
printf("小端\n");
else
printf("大端\n");
return 0;
}
c
//优化
int check_sys()
{
int a = 1;
return (*(char*)&a);//返回1是小端,返回0是大端
}

内存中观察 内存中观察 内存中观察

看得出来是小端
练习2
先看一下整型提升
整型提升
在表达式里,比 int 小的整数类型,会自动先提升为 int,再参与运算。
适用类型:
- char、signed char、unsigned char
- short、unsigned short(int 及更大类型不动)
char/short 在运算时先变 int,有符号就符号扩展,无符号就零扩展。
c
#include <stdio.h>
int main()
{
char a = -1;
signed char b = -1;
unsigned char c = -1;
printf("a = %d, b = %d, c = %d", a, b, c);
return 0;
}
打印的会是多少? 打印的会是多少? 打印的会是多少?

%d - 是以十进制的形式打印有符号的整数(认为我们要打印的数据在内存中是以有符号数的补码进行存储的)
char a = -1;
-
10000000 00000000 00000000 00000001
11111111 11111111 11111111 11111110
11111111 11111111 11111111 11111111
11111111 - a(补码) -
%d ,以十进制形式输出一个有符号整数,按照符号位对char整型提升 11111111 11111111 11111111 11111111
-
这是补码,变成原码打印,取反 + 1,10000000 00000000 00000000 00000001 输出为-1
signed char = char 同理
unsigned char c = -1;
- 10000000 00000000 00000000 00000001
11111111 11111111 11111111 11111110
11111111 11111111 11111111 11111111
11111111 - c(截断)
00000000 00000000 00000000 11111111 (unsigned char无符号整型提升为0)
无符号原码=反码=补码,直接打印
练习3
c
#include <stdio.h>
int main()
{
char a = -128;
printf("%u\n", a);
return 0;
}
char a = -128;
- 10000000 00000000 00000000 10000000
11111111 11111111 11111111 01111111
11111111 11111111 11111111 10000000
10000000 - a
11111111 11111111 11111111 10000000(%u,直接打印,补码=原码)
printf("%u\n", a);//4,294,967,168
%u 是以十进制的形式打印无符号的整数 - 内存中存储的是无符号数的补码
练习4
c
#include <stdio.h>
int main()
{
char a = 128;
printf("%u\n", a);
return 0;
}
char a = 128; char范围:-128~127
- 00000000 00000000 00000000 10000000 (128补码)
10000000 - a
11111111 11111111 11111111 10000000 (char是有符号的,按照符号位整型提升)
%u 十进制、无符号整数的格式符,直接打印
printf("%u\n", a); 打印为4,294,967,168
成环


看这俩串代码
c
#include <stdio.h>
#include <string.h>
int main()
{
char a[1000];
int i;
for (i = 0; i < 1000; i++)
{
a[i] = -1 - i;
printf("%d ", a[i]);
}
return 0;
}

c
#include <stdio.h>
#include <string.h>
int main()
{
char a[1000];
int i;
for (i = 0; i < 1000; i++)
{
a[i] = i;
printf("%d ", a[i]);
}
return 0;
}

练习5
c
#include <stdio.h>
#include <string.h>
int main()
{
char a[1000];
int i;
for (i = 0; i < 1000; i++)
{
a[i] = -1 - i;
}
printf("%d", strlen(a));
return 0;
}

char a[1000];
- 成环范围:-128~127
- -1 -2 -3 ... -128 127 126 ...3 2 1 0 -1 -2
- printf("%d", strlen(a));//求字符串的长度,其实是统计\0(0)之前字符的个数
- 128+127=255
练习6
c
#include <stdio.h>
unsigned char i = 0;//0~255
int main()
{
//死循环了
for (i = 0; i <= 255; i++)
{
printf("%u: hello world\n", i);
}
return 0;
}
unsigned 类型永远 ≥ 0,
成环,unsigned char 环:0 ~ 255范围0-255(2⁸ − 1),永远没法超过255

练习7
c
#include <stdio.h>
#include <windows.h>
int main()
{
unsigned int i;
//>=0
for (i = 9; i >= 0; i--)
{
printf("%u\n", i);
Sleep(100);//休眠100毫秒
}
return 0;
}

unsigned 类型永远 ≥ 0
成环,unsigned int环范围0-4294967295(2³² - 1),永远没法超过4294967295
练习 8
c
#include <stdio.h>
//X86环境 小端字节序
int main()
{
int a[4] = { 1, 2, 3, 4 };
int* ptr1 = (int*)(&a + 1);
int* ptr2 = (int*)((int)a + 1);
printf("%x, %x", ptr1[-1], *ptr2);//?
//%x 是以16进制的形式打印数据
return 0;
}

分步解析
- 数组内存布局(X86 小端字节序)
c
int a[4] = {1, 2, 3, 4};
int 占 4 字节,小端序:低字节存低地址
数组在内存中的十六进制表示(连续地址)
c
a[0] = 1 → 01 00 00 00
a[1] = 2 → 02 00 00 00
a[2] = 3 → 03 00 00 00
a[3] = 4 → 04 00 00 00
- 第一部分:ptr1[-1]
c
int *ptr1 = (int *)(&a + 1);
- &a 是整个数组的地址,类型是 int(*)[4]
- &a + 1 跳过整个数组(16 字节),指向数组末尾之后
- ptr1[-1] 等价于 *(ptr1 - 1),回退一个 int,指向 a[3]
结果:4
- 第二部分:*ptr2(核心难点)
c
int *ptr2 = (int *)((int)a + 1);
- (int)a:把数组首地址强转为整数
- +1:地址向后移动 1 个字节
- 再强转成 int*,从这个新地址读取 4 个字节
内存偏移后读取的数据:
原数组开头:01 00 00 00 02 00 00 00 ...
偏移 1 字节后:00 00 00 02
小端序解析:
02 00 00 00 → 整数 2
按十六进制 %x 打印 → 2000000
结果:2000000
c
printf("%x, %x", ptr1[-1], *ptr2);
// 输出:4, 2000000
严重错误 严重错误 严重错误

用x64环境会导致(int) a 截断,将8字节指针截断为四字节int大小的指针,所以此题是设计好的题
