申明一个结构体:
1 | struct fraction{ |
下面是一些对fraction结构体的操作:
1 | fraction f; |
下面逐行解释下上面的程序。
首先我们声明了类型是fraction的变量f,在栈上分配了两个4字节去存储,低位是denom,高位是num:
| mem |
| — |
| num |
| denom |
接下来是两个赋值语句,内存变成了:
| mem |
| — |
| 7 |
| 22 |
接下来是个复杂的赋值语句,&(f.num)拿到了num所在内存的地址,(fraction*)告诉编译器这个地址是上放的是fraction结构体,->denom是修改这个虚拟结构体的denom字段。好了,内存变成了:
| mem |
| — |
| 12 |
| 22 |
所以紧接着那句输出是12
接下来又是个类似的复杂赋值语句,不用详细解释了,内存变成了:
| mem |
| — |
| 33 |
| 12 |
| 22 |
使用原来的f能不能访问到33这个值呢?能,方法就是最后一个语句。(&f)取到f的地址,(&f)[1],编译器会向后偏移f指向的对象的大小,fraction大小是八字节,所以这句就是往后偏移八个字节,于是乎指向33所在的内存。最后,拿出denom的值。
数组在内存中的布局比较简单,很直接。需要注意的是C、C++并不检查数据越界,访问外面的数据会破坏上下文的数据或者访问不能访问的内存,很危险。
总结下数组和指针操作的等价性:
1 | arr ≡ &arr[0] |
其中,k可以为负数。
下面看个示例:
1 | int arr[5]; |
申明了五个int的数组,&arr[1]拿到第二个元素的地址,(char*)让编译器认为当前指针是char*类型,+8操作就是往后偏移8个字节,这时,指针指向了arr[3]。(short*)让编译器认为当前指针是short*类型,[2]和+2是等价的,移动2*2个字节,现在指针指向的是arr[4],但是编译器会认为这里是个short并赋值为100。所以arr[4]的内存就是0x64 0x00 0x00 0x00(小端)
下面一句类似,[3]就是移动2*3个字节,指向了arr[4]的后半段,赋值1,内存就是0x64 0x00 0x01 0x00
输出语句就是把arr[4]里面所有的内容以int的方式输出1 * 2 ^ 16 + 100 = 65636
最后,我们看一个综合的示例:
1 | struct student{ |
内存布局类似下面的形式(表格并没有完全对齐。。。):
| mem | 4 bytes | per | row |
| — |-|-|-|
| numUnits |
| s | s | u | u |
| i | i | d | d |
| name |
| numUnits |
| s | s | u | u |
| i | i | d | d |
| name |
下面的是[0]上面的是[1]
经过第一个输出之前的三个赋值语句后:
| mem | 4 bytes | per | row |
| — |-|-|-|
| numUnits |
| s | s | u | u |
| i | i | d | d |
| name |
| 21 |
| 5 | x | x | \0 |
| 4 | 0 | 4 | 1 |
| name |
pupils[1].name指向的是倒数第三行第二个x所在的内存
第一个输出语句很简单,40415xx,遇到\0就结束了
strcpy(pupils[1].name, “123456”);
这个语句的操作结果是:
| mem | 4 bytes | per | row |
| — |-|-|-|
| numUnits |
| s | s | u | u |
| \0 | i | d | d |
| 3 | 4 | 5 | 6 |
| 5 | x | 1 | 2 |
| 4 | 0 | 4 | 1 |
| name |
这时,输出的pupils[0].suid是40415x123456,遇到pupils[1]的\0才结束。
pupils[0].numUnits也已经被修改了,16进制输出是36353433,数字6的ASCII码对应的就是0x36,其他类似,为什么是反着的呢?我的机器是小端的。