写在前面
今天写了一些计组的作业,主要内容是数字的机器级表示。以前大一的时候学过一些,当时还气血方刚的花了100多大洋买了一本《深入理解计算机系统》。如今可以重拾起来啦。
觉得有些东西蛮神奇的,以前写代码时候会遇到一些莫名其妙的错误,比如#define MAX 2147483647会出错,但是#define MAX 1<<31-1就不会出错。希望能够好好学习这门课程,深入理解内部的原理。
这是作业的前五个题,后面的两个题目有浮点数的相关内容,还没写😂
Question 1.
下述两个结构所占存储空间多大?结构中各分量所在位置相对于结构起始位置的偏移量是什么?要求编写程序以验证你的答案。若使用#pragma pack(2)语句,则结果又如何?1
2
3
4
5
6
7
8
9
10
11
12
13
14struct test1
{
char x2[3];
short x3[2];
int x1;
long long x4;
};
struct test2
{
char x2[3];
short x3[2];
int x1;
long long x4;
}__attribute__((aligned(8)));
Answer 1.
在64位操作系统下:
(1) test1:
猜想:
占存储空间13+22+4+8=19字节,&x2=首地址,x3的偏移量为3,x1相对偏移量为7,x4相对偏移量为11。
代码验证:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
struct test1{
char x2[3];
short x3[2];
int x1;
long long x4;
}t1;
struct test2{
char x2[3];
short x3[2];
int x1;
long long x4;
}__attribute__((aligned(8)));
struct test2 t2;
int main()
{
printf("sizeof(test1)=%d\n",sizeof(t1));
printf("&t1=0x%X\n",&t1);
printf("&t1.x2=0x%X\n",t1.x2);
printf("&t1.x3=0x%X\n",t1.x3);
printf("&t1.x1=0x%X\n",&t1.x1);
printf("&t1.x4=0x%X\n",&t1.x4);
printf("-----------------------\n");
printf("sizeof(test2)=%d\n",sizeof(t2));
printf("&t2=0x%X\n",&t2);
printf("&t2.x2=0x%X\n",t2.x2);
printf("&t2.x3=0x%X\n",t2.x3);
printf("&t2.x1=0x%X\n",&t2.x1);
printf("&t2.x4=0x%X\n",&t2.x4);
return 0;
}
结果:
发现与设想的结果不同,一个char类型占1个字节,3个应该占连续的三个字节,但实际上是占了四个字节。Int占4个字节,实际上占了8个字节。所以4+22+8+8=24字节。
通过查询资料,发现:
当未指定#pragma pack时,默认值为8。在该种情况下:
① x2的起始位置偏移量为0,1个char类型是1字节,3个是三字节。
② x3的对齐模数是min{8,sizeof(short)},short类型占2字节,所以x3的对齐模数为2,所以x3的起始地址偏移量为2的整数倍,所以在3个char字符后面补一个空字节,x3的起始地址偏移量为4。
③ x1的对齐模数是4,前面已经有了(13+1)+(22)=8字节,恰好是4的整数倍,所以不需要补空字节,直接往后放,x1的起始地址偏移量为8。
④ x4的对齐模数为8,而前面已经有了(13+1)+(22)+4=12个字节,要是8的整数倍,所以填充4个字节,x4的偏移量为16.
⑤ 所以计算出来结构体test1的大小为(13+1)+(2*2)+(4+4)+8=24字节。其中标红的是填充的空字节。
对于test2,是使用了attribute取消字节对齐,指定按照8字节对齐,发现和上面默认以8字节为对齐是一样的效果。
(2) 代码:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
struct test1{
char x2[3];
short x3[2];
int x1;
long long x4;
}t1;
struct test2{
char x2[3];
short x3[2];
int x1;
long long x4;
}__attribute__((aligned(8)));
struct test2 t2;
int main()
{
printf("sizeof(test1)=%d\n",sizeof(t1));
printf("&t1=0x%X\n",&t1);
printf("&t1.x2=0x%X\n",t1.x2);
printf("&t1.x3=0x%X\n",t1.x3);
printf("&t1.x1=0x%X\n",&t1.x1);
printf("&t1.x4=0x%X\n",&t1.x4);
printf("-----------------------\n");
printf("sizeof(test2)=%d\n",sizeof(t2));
printf("&t2=0x%X\n",&t2);
printf("&t2.x2=0x%X\n",t2.x2);
printf("&t2.x3=0x%X\n",t2.x3);
printf("&t2.x1=0x%X\n",&t2.x1);
printf("&t2.x4=0x%X\n",&t2.x4);
return 0;
}
结果:
对于test1:
因为通过#pragma pack(2)指定了以2对齐,所以分析如下:
① X1起始地址偏移量为0,占13个字节。
② X2对齐模数为min{sizeof(short),2},所以对齐模数是2.所以起始地址偏移量是2的整数倍,即4.所以在x1后填充1字节。
③ X3对齐模数为2,所以直接在后面加,起始地址偏移量为(13+1)+(22)=8.
④ X4对齐模数为2,所以也直接在后面加,起始地址偏移量为(13+1)+(22)+4=12。
⑤ 结构体Test1占的大小为(13+1)+(2*2)+4+8=20字节。
对于test2:
因为通过attribute指定以8对齐,所以和(1)中结果相同。
Question 2.
“-2 < 2”和“-2 < 2u”的结果一样吗?为什么?
Answer 2.
答案
不同。
-2<2:true
-2<2u:false
代码验证
#include
int main()
{
printf(“-2<2: %d\n”,-2<2);
printf(“-2<2u: %d\n”,-2<2u);
return 0;
}
结果
-2<2: 1
-2<2u: 0
分析
在第一个比较中,-2和2 都会被认为是带符号数,所以在计算机中以补码存储。
-2的补码:1110 2的补码:0010
所以-2<2是真。
在第二个比较中,2u是无符号数2,所以是以原码形式存储,而-2也被解析为无符号数,假设在4bit的情况下,-2为1110,按照原码解析为十进制数的14.2的原码为0010,被解析为2.所以-2<2u是假。
Question 3.
运行下图中的程序代码,并对程序输出结果进行分析。1
2
3
4
5
6
7
8
9
10
11
12
void main()
{
unsigned int a=1;
unsigned short b=1;
char c=-1;
int d;
d = (a > c) ? 1:0;
printf("unsigned int is %d\n",d);
d = (b > c) ? 1:0;
printf("unsigned short is %d\n",d);
}
Answer 3.
运行结果
分析
知识点:
① 不同数据类型进行比较时,需要整型提升(integral promotion)和寻常算术转换(usual arithmetic conversion)。转换的规则是向上转换至运算双方均可以容纳的下的类型,并且char、short等类型在表达式中按照int或unsigned int 计算。比如unsigned short和char进行比较,双方均会提升为int类型。
② 在补全位的时候,有符号数补符号位,无符号数补0.
具体分析:
a是unsigned Int类型,c是char类型,a与c比较时,c会升级为unsigned类型,再与a比较。c是char型-1,按照有符号数计算,且符号位为1,所以补全到8字节后,c为0xFFFFFFFF。a是unsigned int类型,机器码为0x0000001。两者按照无符号数比较,a被解析为1,c被解析为4294967295,显然a>c为假。
b是unsigned short类型,c是char类型,b与c比较时,两边都会升级为int类型,然后按照有符号数比较。C是char型-1,机器码为0xFFFFFFFF,被解析为-1,b是unsigned short类型1,机器码为0x00000001,被解析为1,所以b>c是正确的。
Question 4.
运行下列代码,并对输出结果进行分析。1
2
3
4
5
6
7
8
9
10
11
void main()
{
union NUM
{
int a;
char b[4];
} num;
num.a = 0x12345678;
printf("0x%X\n", num.b[2]);
}
Answer 4.
结果
0x34
分析
是union联合体类型,所以int a和char b[4]共同占用相同的4字节的空间。当改变a的值为0x12345678时,该4字节的空间被赋值。而b[2]是取第三块一字节的值,所以为0x34。
Question 5.
请说明下列赋值语句执行后,各个变量对应的机器数和真值各是多少?编写一段程序代码并进行编译,观察默认情况下,编译器是否报warning。如果有warning信息的话,分析为何会出现这种warning信息。1
2
3
4
5int a = 2147483648;
int b = -2147483648;
int c = 2147483649;
unsigned short d = 65539;
short e = -32790;
Answer 5.
报warning信息:
分析:
① 对于第一个报错,是对int a=2147483648的报错。因为2147483648已经超过了int的上限,会按照C90标准将这个数转为无符号的十进制数进行处理。
② 在C90中,无后缀数会被依次解析为:int/long int/unsigned long int。而在C99中,无后缀数会被解析为int/long int/long long int。区别在于,C90中如果数字很大,会被按照unsigned long int 解析,而unsigned long int 是无符号数。在C99中,三种可能的类型均为带符号数。而且,对于-2147483648这个特殊的数,是按照一元减法表达式来解析的,而不是按照一个数字来解析。所以编译器在解释的时候,会把它解析为一个无符号数2147483648然后再做减法。
③ 同①。
④ 类似①,但结果和原因不同。d最终为3,是因为d本身类型为unsigned short是无符号数,而short为两个字节,unsigned short最大为65535.65539的机器码为0x10003,最高位的1丢弃,只剩0x0003,所以值为3.
⑤ Short的范围为-32768~32767.而给e赋值为-32790,显然已经超过了范围。-32790的补码为0111 1111 1110 1010,按照有符号数short解析为32746.
解决方案:
① 1) Int a=2147483648改为int a=0x80000000,不会报warning,输出int a的值为-2147483648
2) int a=2147483648改为int a=2147483648U。输出int a的值为-2147483648.
② 1)改为-2147483647-1,因为2147483647和1均在int范围内,运算都在int内运算,不会报warning。
2)在-2147483648后加后缀U。
③ 同①
④ 给d换一个更大的数据类型,或者把赋值的数弄在范围内。
⑤ 同④
疑问:
为什么在第5个warning中,我把short e=-32790改为short e=32790,不报任何错呢?
最终效果:
代码:
1 |
|
编译信息:
输出:
附录–C语言中不同变量类型的输出格式
符号属性 | 长度属性 | 基本型 | 所占位数 | 取值范围 | 输入符举例 | 输出符举例 |
---|---|---|---|---|---|---|
– | – | char | 8 | -2^7~2^7-1 | %c | %c/%d/%u |
signed | – | char | 8 | -2^7~2^7-1 | %c | %c/%d/%u |
unsigned | – | char | 8 | 0~2^8-1 | %c | %c/%d/%u |
[signed] | short | [int] | 16 | -2^15~2^15-1 | %hd | %hd |
unsigned | short | [int] | 16 | 0~2^16-1 | %hu/%ho/%hx | %hu/%ho/%hx |
[signed] | – | int | 32 | -2^31~2^31-1 | %d | %d |
unsigned | – | [int] | 32 | 0~2^32-1 | %u/%o/%x | %u/%o/%x |
[signed] | long | [int] | 32 | -2^31~2^31-1 | %ld | %ld |
unsigned | long | [int] | 32 | 0~2^32-1 | %lu/%lo/%lx | %lu/%lo/%lx |
[signed] | long long | [int] | 64 | -2^63~2^63-1 | %lld/%I64d | %lld/%I64d |
unsigned | long long | [int] | 64 | 0~2^64-1 | %llu/%llo/%llx | %llu/%llo/%llx |
– | – | float | 32 | +/-3.40282e+038 | %f/%e/%g | %f/%e/%g |
– | – | double | 64 | +/-1.79769e+308 | %lf/%le/%lg | %lf/%le/%lg |
– | – | long double | 96 | +/-1.79769+308 | %Lf/%Le/%Lg | %Lf/%Le/%Lg |