练习1int main() { char a -1; signed char b -1; unsigned char c -1; printf(a%d,b%d,c%d, a, b, c); return 0; }1. 存储规则内存中存的都是补码char类型占1 字节 8 个比特位整数在内存中一律以补码形式存储。 数值-1的 8 位补码计算原码1000 0001最高位是符号位1 表示负反码1111 1110符号位不变其余位取反补码1111 1111反码 1因此三个变量a、b、c在内存里存储的二进制完全一样都是1111 1111区别只在于解读方式。2. 补充printf 的整型提升printf用%d输出时要求参数是int32 位类型char传参会自动发生整型提升有符号char提升高位补符号位符号位是 1 就全补 1 8 位1111 1111→ 32 位11111111 11111111 11111111 11111111作为int补码解析最终打印在屏幕上是原码的结果因为最高位是1为负数所以原码补码取反1且符号位不变:10000000 00000000 00000000 00000001值仍然是-1。无符号char提升高位统一补08 位1111 1111→ 32 位00000000 00000000 00000000 11111111作为int解析因为最高位是0为正数所以原反补码都相同依然是00000000 00000000 00000000 11111111所以值是255。练习2int main() { char a -128; printf(%u\n, a); return 0; }1. 存储-128 在 char 中的补码-128原码表示:10000000 00000000 00000000 10000000反码:11111111 11111111 11111111 01111111补码:10000000 00000000 00000000 10000000char类型取一个字节:10000000所以变量a在内存中存储的 8 位二进制是1000 00002. 传参发生整型提升printf的%u对应unsigned int类型char类型传参会自动整型提升为 32 位int。有符号 char 提升规则高位补符号位符号位是 1高位就全部补 18 位1000 0000提升为 32 位补码后11111111 11111111 11111111 10000000这个 32 位二进制作为有符号 int解读值仍然是-128。3. 输出% u 按无符号解读%u会把内存中的 32 位二进制直接当成无符号整数计算数值练习3int main() { char a 128; printf(%u\n, a); return 0; }1. 赋值溢出截断signed char的取值范围是-128 ~ 127128超出了有符号 char 的最大值发生溢出截断。 整数 128 的二进制低 8 位为1000 0000存入 char 变量时只保留这 8 位。 根据 8 位有符号补码规则1000 0000固定表示-128。 因此变量a内存中的实际值等价于-128。2. 传参整型提升printf传参时char类型会自动整型提升为 32 位int。 有符号 char 的提升规则高位补符号位符号位为 1高位全部补 1。 8 位1000 0000提升为 32 位补码11111111 11111111 11111111 10000000这个 32 位二进制作为有符号 int 解读值仍然是-128。3. 输出%u按无符号解析%u格式符会将 32 位二进制直接当作无符号整数计算数值结果与第二题一样练习4int main() { char a[1000]; int i; for (i 0; i 1000; i) { a[i] -1 - i; } printf(%d, strlen(a)); return 0; }1. strlen 的工作规则strlen从数组首地址开始逐个字节计数遇到值为0即\0的字节时停止返回停止位置之前的字符总数不包含\0本身。2. 数组元素的赋值规律数组是char类型默认signed char1 字节 8 位取值范围-128 ~ 127赋值规则为a[i] -1 - i。 但是计算结果会超出signed char范围。3. 数值变化过程当i从 0 到 127 时a[i]的值依次为-1, -2, -3 ... -128都在有符号 char 的范围内正常存储。当i128时 计算值为-129超出范围溢出后等价于-129 256 127即a[128] 127。当i从 129 继续增大时 数值依次递减126, 125 ... 2, 1。当i254时:a[254]的值是1当i255时:a[255]的值是04. 最终计数数组中第一个值为 0 的元素是a[255]strlen统计到此处停止。 从a[0]到a[254]一共 255 个非 0 字符因此strlen(a)的结果是255。练习5unsigned char i 0; int main() { for (i 0; i 255; i) { printf(hello world\n); } return 0; }1. 变量类型的取值范围unsigned char是 8 位无符号字符类型取值范围固定为0 ~ 255所有可能的取值都满足i 255这个判断条件。2. 循环的溢出回绕循环执行过程i从 0 开始递增依次执行循环体打印内容当i 255时仍然满足i 255会进入循环体执行打印循环体结束后执行i255 1超出了 8 位无符号数的范围发生无符号溢出 8 位二进制1111 1111加 1 后变为1 0000 0000截断低 8 位后结果为0000 0000即i的值变回 0。3. 死循环的形成i的值会在 0 → 255 → 0 → 255 之间无限循环永远不会出现i 255的情况循环条件恒为真程序永远不会退出循环。练习6int main() { unsigned int i; for (i 9; i 0; i--) { printf(%u\n, i); } return 0; }1. 无符号整数的取值特性unsigned int32 位环境的取值范围是0 ~ 4294967295它的所有合法取值都天然满足i 0因此循环判断条件i 0永远为真。2. 无符号下溢回绕当循环执行到i 0时依然满足i 0进入循环体打印0循环体结束后执行i--即0 - 1 -1无符号整数不支持负数-1会被当作无符号整数类型-1的补码是32个1:11111111 11111111 11111111 11111111因为被当做无符号整数所以-1就会转换成32个1也就是一个特别大的数42949672953. 死循环形成i从 9 递减到 0 后不会退出循环而是溢出回绕成最大值继续从4294967295往下递减。 由于无符号数永远不可能小于 0循环条件恒成立程序就会无限执行下去。练习7//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); return 0; }一、ptr1[-1]的结果4指针类型与加减规则a是整个数组的地址类型为int (*)[4]指向长度为 4 的 int 数组的指针。 指针加 1 的步长由指针类型决定a 1会跳过整个数组的大小4 个 int共 16 字节指向数组a末尾之后的第一个位置。强制类型转换与下标访问将a 1强制转为int*赋值给ptr1此时ptr1指向数组末尾后的第一个 int 位置。ptr1[-1]等价于*(ptr1 - 1)int 指针减 1向前回退 4 字节刚好指向数组的最后一个元素a[3]其值为4十六进制输出为4。二、*ptr2的结果0x2000000输出为2000000地址数值加 1 ≠ 指针加 1a是数组首元素地址类型为int*。(int)a将地址强制转换为整数此时1是纯数值加 1也就是地址仅向后偏移1 个字节而非一个 int 的长度。再转回int*后ptr2指向a[0]首地址 1 字节 的位置。小端字节序的内存布局int 占 4 字节x86 是小端模式数据的低字节存在内存低地址高字节存在内存高地址。 数组a的内存字节布局地址从低到高地址偏移01234567所属元素a[0]a[0]a[0]a[0]a[1]a[1]a[1]a[1]字节值0x010x000x000x000x020x000x000x00跨字节读取 intptr2从偏移 1 的位置开始读取 4 个字节取到的字节依次是0x00、0x00、0x00、0x02。 按小端规则低地址字节对应 int 的低位高地址字节对应 int 的高位组合后的 int 值为0x02000000十六进制输出为2000000