第三讲
这两节课咱们再来学习一下C语言。
double d = 3.1416;
char ch = *(char*)&d;
cout << ch << endl;
这段代码,跟上节课一样,char只存储double的低8位数据
short s = 45;
double d = *(double*)&s;
cout << d << endl;
这里的话,同样,45保存到double的低16位,就会是非常小的一个数字
一般来说,这种方式在大小端存储中会有不同, 比如Linux下的1可能会在Solaris中成为256,
其实大端小端,大尾小尾机器在上计算机组成原理时就模模糊糊的,这里也不是太懂,我一直是按照左低右高作为记忆,不管它了,以我的测试机器运行结果为准吧。
结构体
struct fraction {
int num;
int denum;
};
fraction pi;
pi.num = 22;
pi.denum = 7;
结构体可以认为一段连续存储域,执行pi.num = 22时,机器无法区分这是pi.num还是整个pi的存储地址,实际上,是在pi的存储域的基地址偏移量为0的4byte区域,赋值为22,因为机器知道这是pi.num的存储区。同样赋值pi.denum时,是在整个存储域的基地址偏移量为4的4byte区域赋值。
小高能
((fraction*)&(pi.denum))->num = 12;
cout << "pi.denum is :" << pi.denum << endl;
这段代码,首先取pi.denum的地址,是一个(int*)类型的指针,我们强制转换成fraction*类型,之后对这个fraction*区的num赋值12。下图中,深灰色为&pi.denum, 浅灰色是&fraction,那么可以看出,*fraction->num就是原来的pi.denum,那么结果就是:pi.denum = 12。
运行结果:
再执行下面语句:
((fraction*)&(pi.denum))->denum = 33;
那么存储区会是这个样子:
机器会默认*fraction->denum合法的,因此在fraction*的基地址偏移地址为4的4byte区赋值33,虽然在程序中并没有对应的存储方式,但是在内存中该地址的值已经变为33了。
数组
int array[10];
array[0] = 44;
array[9] = 100;
array = &array[0],相信学过的都知道了,array其实是指向数组存储区的首地址的指针,在函数调用时,数组传入的实际上是&array[0],并非整个array。
array[5] = 45;
array[10] = 1;
机器会尝试在基地址偏移量为10,即40个byte之后的4byte区赋值,但是C语言中原始数组并不提供边界查询,会不会产生问题又是另一码事了。
array[25] = 25;
array[-4] = 77;
这段代码同样,机器会尝试执行代码,但是不会检查边界,同上的原理,尝试在基地址前面16个byte或者后100个byte后的4byte区域内赋值。
同样,array + k = &array[k],即array[k]实际上是寻找array[0]地址向后偏移k单位的内存区。
*array = array[0];
*(array + k) = array[k];
那之前array[-4] = 77,其实就是*(array - 4) = 77了。
int arr[5];
arr[3] = 128;
((short*)arr)[6] = 2;
cout << arr[3] << endl;
这段代码,将int数组强制转换为short类型的,可以知道,128是在arr[3]的低16位中的,那么((short*)arr)[6]指向的是arr[3]的高16位,对其赋值2,那么相当于整个arr[3]多赋值了512,打印结果应该是512 + 128
实验结果并非如此:
首先,((short*)arr)[6]对应高16位的话,即使相加也是应该增大2^17次方,即131072,并且在小端存储机器中,低地址对应低位,存储结构应该是这样的:
我们执行以下代码,如果我的推论没错的话,结果应该是131072 + 128 = 131200;
int arr[5];
arr[3] = 128;
((short*)arr)[7] = 2;
cout << arr[3] << endl;
cout << pow(2, 17) << endl;
结果:
推论正确,老师说的可能是大端机器的运行结果,结果并不重要,理解强制类型转换在数组内的作用即可,知道数组内部存储方式和寻址方式是最重要的。
小高能
((short*)(((char*)(&arr[1])) + 8))[3] = 100;
首先&arr[1]是取arr[1]的存储地址,类型是(int*),将它强制转换成(char*)类型,再向后寻址8个单位即32byte,即为原来数组的arr[5]的地址,再强制转换为(short*)类型,则这时新“数组”的基地址就为&arr[5]了并对其后面3个单位偏移量的内存赋值100。
再谈结构体
struct student {
char *name;
char suid[8];
int numUnits;
};
student pupils[4];
pupils[0].numUnits = 2;
pupils[2].name = strdup("Adam");
pupils[3].name = pupils[0].suid + 6;
strcpy(pupils[1].suid, "40415xx");
执行这段代码,内存区会是这样子的:
再继续执行下面这段代码:
strcpy(pupils[3].name, "123456");
注意,上面pupils[3].num已经指向为pupils[0].suid + 6,那么当执行上面这段代码时,就是从pupils[0].suid + 6的地址向后6个字节存储"123456",
然后pupils[0].nameUnits会是一个非常大的数字3 * 2^24 + 4 * 2^16 + 5 * 2^8 + 6;
这里我认为3 4 5 6应该不是直接传进去,应该是它们对应的ascii码值,即 51 52 53 54。
pupils[7].suid[11] = 'A';
这里仍然是以pupils[0]为基地址偏移7个单位的一个结构体存储区,它的suid[0]为基地址偏移量为11的存储区赋值为'A';
机器是这样子理解上面的代码的,但是实际运行肯定会有问题的。
void swap(int *ap, int *bp) {
int temp = *ap;
*ap = *bp;
*bp = temp;
}
int x = 7;
int y = 117;
swap(&x, &y);
无论是int还是float,double,struct,交换模式都是位模式,并无区别。