C拾遗 -- 2 数组和指针(1)

2016年05月05日

一、前言

  • 指针对于那时刚学习C的我来说,简直是个黑洞,数组和指针更是给我留下了是这样的印象:
    • 数组相当于指针
  • 本文主要说明数组与指针的区别,以及列举错误使用的方式并分析原因

二、正文

1. 左值与右值

  • 区分左值和右值需要结合上下文
    • 编译时可知:左值
      • 数组名,变量名的地址都是编译时可知,属于左值
    • 运行时才知:右值
      • 变量的值,临时量都是在运行时才知,属于右值
    • x=y;
      • x代表地址,编译时可知,是左值;y代表地址的内容,运行时才知,是右值
  • 右值永远都不会是左值!

2. 数组名是不可修改的左值,指针是左值

  • 数组名是一个不可修改的左值
    • 数组名是一个地址常量(属于符号常量),代表存放该数组的那块内存的首地址
    • 不能作为被赋值的对象
  • 指针作为变量,是一个左值

3. 数组和指针的引用方式

  • 数组的引用方式
    • 取得符号表中a的逻辑地址(0x91C4018),也就是数组首地址
    • 把下标表示的偏移量(+3)与指针的值相加,产生一个新的地址(0x91C401B)
    • 访问新的地址,取得字符n
    • 即*(0x91C4018 + 3 * sizeof(char)) = *(0x91C4018 + 3 * 1) = *(0x91C401B) = n
  • 指针的引用方式(机器配置:i5-5257U,是小端模式)
    • 取得符号表中p的逻辑地址(0xFAD6020),提取该指针的值(另一个逻辑地址,即数组的首地址,0xFAD6018)
    • 把下标表示的偏移量(+3)与指针的值相加,产生一个新的地址(0xFAD601B)
    • 访问新的地址,取得字符n
    • 即*( *(0xFAD6020) + 3 * sizeof(char)) = *(0xFAD6018 + 3 * 1) = *(0xFAD601B) = n

4. 声明与定义不一致

  • extern数组
    • 代码及运行结果
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# definition_pointer.c
#include <stdio.h>
char b[] = "1234";
char *a = b;
extern void f();
int main()
{
    printf("\ndefinition as char *:\n");
    printf("a ptr address : 0x%x , a ptr to :0x%x\n" , &a , a);
    for(int i = 0 ; i < 4 ; ++i)
    {
        printf("0x%x=%c " , a+i , a[i]);
    }
    printf("\ndeclaration as char []:\n");
    f();
}
1
2
3
4
5
6
7
8
9
10
11
# declaration_array.c
#include <stdio.h>
extern char a[];
void f()
{
    for(int i = 0 ; i < 4 ; ++i)
    {
        printf("a[%d] = %x " , i , a[i] & 0xFF);
    }
    printf("\n");
}
1
cc definition_pointer.c declaration_array.c && ./a.out
1
2
3
4
5
definition as char *:
a ptr address : 0x7f6f020 , a ptr to :0x7f6f018
0x7f6f018=1 0x7f6f019=2 0x7f6f01a=3 0x7f6f01b=4
declaration as char []:
a[0] = 18 a[1] = f0 a[2] = f6 a[3] = 7
  • 分析
    • 在definition_pointer.c的上下文中,a是一个指针变量,a的地址为0x7f6f020,a指向b数组的首地址0x7f6f018
    • 在declaration_array.c的上下文中,a是一个数组,但有extern修饰,编译的时候不会分配内存,在链接的时候到别的文件中寻找符号a的实际地址,在definition_pointer.o能找到0x7f6f020
      • 在declaration_array.c执行a[0],会按照数组的引用方式,实际获得的是b数组首地址的最低位字节(小端模式)

  • extern指针
    • 代码及运行结果
1
2
3
4
5
6
7
8
9
10
11
12
# efinition_array.c
#include <stdio.h>
char a[] = {0 , 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 ,10 , 11 , 12 , 13 , 14 , 15};
extern void f();
int main()
{
    for(int i = 0 ; i < sizeof(a) / sizeof(a[0]) ; ++i)
    {
        printf("0x%x=%d " , a+i , a[i]);
    }
    f();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
# declaration_pointer.c
#include <stdio.h>
extern char *a;
void f()
{
    printf("\nsizeof(char)=%d\n" , sizeof(char));
    printf("sizeof(char *)=%d\n" , sizeof(char *));
    printf("sizeof(&a)=%d \t\t &a=0x%x\n" , sizeof(&a) , &a);
    printf("sizeof(&a+1)=%d \t\t &a+1 =0x%x\n" , sizeof(&a+1) , &a+1);
    printf("sizeof(*(&a+1))=%d \t *(&a+1) = 0x%x\n" , sizeof(*(&a+1)) ,*(&a+1));
    printf("sizeof(a)=%d \t\t a=0x%x\n", sizeof(a) , a);
    printf("sizeof(a+1)=%d \t\t a+1=0x%x\n",sizeof(a+1) , a+1);
}
1
cc definition_array.c declaration_pointer.c && ./a.out
1
2
3
4
5
6
7
8
0x8347020=0 0x8347021=1 0x8347022=2 0x8347023=3 0x8347024=4 0x8347025=5 0x8347026=6 0x8347027=7 0x8347028=8 0x8347029=9 0x834702a=10 0x834702b=11 0x834702c=12 0x834702d=13 0x834702e=14 0x834702f=15
sizeof(char)=1
sizeof(char *)=8
sizeof(&a)=8 		 &a=0x8347020
sizeof(&a+1)=8 		 &a+1 =0x8347028
sizeof(*(&a+1))=8 	 *(&a+1) = 0xb0a0908
sizeof(a)=8 		 a=0x3020100
sizeof(a+1)=8 		 a+1=0x3020101
  • 分析
    • 在definition_array.c的上下文中,a是一个数组,首地址为0x8347020
    • 在declaration_pointer.c的上下文中,a是一个指针变量,但有extern修饰,编译的时候不会分配内存,在链接的时候到别的文件中寻找符号a的实际地址,在definition_array.o能找到0x8347020,会按照指针的引用方式,进行操作
      • &a:链接过程会找到数组的首地址0x8347020,但这里a声明为作为指针变量
      • &a+1:指针变量的大小是8 Bytes,所以值为0x8347020+0x8=0x8347028
      • *(&a+1):从0x8347028取得指针变量的值(低32bit),按照小端模式,得出为0xB0A0908
      • a:从0x8347020取得指针变量的值(低32bit),按照小端模式,得出为0x3020100
      • a+1:a声明为指向char类型的指针,char大小为1 Byte,所以a+1=0x3020100+sizeof(char)=0x3020101

  • 总结
    • extern数组和extern指针,总是按照上下文中的声明进行引用,而不理会原先的定义