反复在数组名与指针上犯错误,特记录下。
int a[] = {1,2,3,4,5}; int *p, *q; p = (int *)(a+1); q = (int *)(&a-1);
*(p+1)? *(q-1) ?
答案是 3, 5。这里主要涉及的问题就是指针参与运算时,它的地址是怎么增加的,也就是它的地址变化单位。首先,明确一点,指针参与算术运算时,指针地址变化是:offset * 指针指向对象长度。例如,上面的代码中,p, q都是int *类型的,那么指针对象长度就是4,同理,char *ptr, 它的就是1。再看看p q此时的指向,关键点就是对于数组名,a和&a有和不同?它们两的值其实是一样的,但是指向的数据类型就不一样了,a指向的是数组第一个元素,也就是说a = &(a[0]),而&a呢,指向的数据类型是int a[5]这样一个数组,而a[0]是个int型数据,所以,a: int * , &a: int [5] *,那么它们指向对象的长度就分别是4,和20了。问题解决。
此外,sizeof(a) ? sizeof(&a) ?
答案是 20, 4。sizeof发生在程序的编译阶段,求指定对象占的存储空间。a对象就是整个数组,&a对象就是指向这个数组对象的指针。所以答案就是20, 4。貌似这一切都是一些规定性的东西,说不出一个好的彻底的理由。
还有一点就是数组名是一个常量指针。额,不是没用const修饰吗?其实说是常量指针不够严谨,应该是只读指针。被const修饰意味着只读。看看其汇编就明白了。
1 .arch armv4t 2 .fpu softvfp 3 .eabi_attribute 20, 1 4 .eabi_attribute 21, 1 5 .eabi_attribute 23, 3 6 .eabi_attribute 24, 1 7 .eabi_attribute 25, 1 8 .eabi_attribute 26, 2 9 .eabi_attribute 30, 6 10 .eabi_attribute 18, 4 11 .file "a.c" 12 .text 13 .align 2 14 .global main 15 .type main, %function 16 main: 17 @ Function supports interworking. 18 @ args = 0, pretend = 0, frame = 24 19 @ frame_needed = 1, uses_anonymous_args = 0 20 @ link register save eliminated. 21 stmfd sp!, {r4, fp} 22 add fp, sp, #4 23 sub sp, sp, #24 24 ldr r3, .L3 //ldr伪指令,将数组所在段的标签地址加载到r3,.L3这个标签可以理解为数组名 25 sub ip, fp, #24 26 mov r4, r3 27 ldmia r4!, {r0, r1, r2, r3} 28 stmia ip!, {r0, r1, r2, r3} 29 ldr r3, [r4, #0] 30 str r3, [ip, #0] 31 mov r3, #0 32 mov r0, r3 33 sub sp, fp, #4 34 ldmfd sp!, {r4, fp} 35 bx lr 36 .L4: 37 .align 2 38 .L3: 39 .word C.0.1261 40 .size main, .-main 41 .section .rodata 42 .align 2 43 .type C.0.1261, %object 44 .size C.0.1261, 20 45 C.0.1261: 46 .word 1 47 .word 2 48 .word 3 49 .word 4 50 .word 5 51 .ident "GCC: (ctng-1.6.1) 4.4.3" 52 .section .note.GNU-stack,"",%progbits
这个数组的定义位于上面汇编的38-50行,可以看到41行对此段声明为rodata。未完,39-40行的修饰应该是针对标签 .L3。熟悉下汇编后再来完善。
为什么不能修改数组名的值?从C语言的角度,如果修改了数组名的值,例如a++,也就是a = a + 1;那么此后,数组a将失去其基地址,后面的代码将无法访问a[0]了。也许你会说,a = a -1;不就找到了吗?但是每次修改后又要恢复,岂不是极其麻烦,容易导致编程的人疏漏出错。所以编译器干脆规定不允许修改。