指针与地址


在〈变量〉曾经说过,变量提供具名称的内存存储空间,一个变量关联一个数据类型、存储的值与存储空间的地址值。

如果想知道变量的内存地址,可以使用&取址运算符(Address-of operator),例如:

#include <stdio.h>

int main(void) {
    int n = 10;

    printf("n 的值:%d\n", n);
    printf("n 的地址:%p\n", &n);

    return 0;
}

执行结果:

n 的值:10
n 的地址:0061FECC

这个程序中,定义了一个int整数变量nn存储的内存地址是 0061FECC,这是 16 进制表示法,如果int长度是 4 个字节,从 0061FECC 后的 4 个字节是n配置到的内存空间,现在这个空间中存储值为 10。

直接访问变量会对分配到的空间作访问,指针(Pointer)是一种变量,存储内存地址,要定义指针,是使用以下的语法:

type *ptr;

ptr可存储地址,而type为该地址存储值的类型,实际定义的方式如下:

int *n;
float *s;
char *c;

虽然定义指针时,C 习惯将*前置在变量名称前,不过n的类型是int*s的类型是float*,而c的类型是char*,指针的类型决定了地址上的数据如何解释,以及如何进行指针运算(Pointer arithmetic)。

可以使用&运算符取出变量的地址值并指定给指针,例如:

#include <stdio.h>

int main(void) {
    int n = 10;
    int *p = &n ;

    printf("n 的地址:%p\n", &n);
    printf("p 存储的地址:%p\n", p);

    return 0;
}

执行结果:

n 的地址:0061FEC8      
p 存储的地址:0061FEC8

以上的程序使用&来获取变量n的地址,然后指定给指针p,因此p存储的地址就与&n获取的地址相同。

可以使用提取 (Dereference)运算符*来提取指针存储的地址中之数据,例如:

#include <stdio.h>

int main(void) {
    int n = 10;
    int *p = &n;

    printf("指针 p 存储的值:%p\n", p);
    printf("取出 p 存储地址处之值:%d\n", *p);

    return 0;
}

执行结果:

指针 p 存储的值:0061FEC8
取出 p 存储地址处之值:10

如果已经获取了内存地址,将某值指定给*P时,该内存地址的值也会改变,这相当于告诉程序,将值放到P存储的地址处,例如:

#include <stdio.h>

int main(void) {
    int n = 10;
    int *p = &n ;

    printf("n = %d\n", n);
    printf("*p = %d\n", *p);

    *p = 20;

    printf("n = %d\n", n);
    printf("*p = %d\n", *p);

    return 0;
}

执行结果:

n = 10
*p = 10
n = 20
*p = 20

当指针p存储的地址与变量n的地址相同时,对*p进行指定,就会将值直接存入该内存位置,因此透过变量n取出的值也就改变了。

如果定义指针但不指定初值,则指针存储的地址是未知的,访问未知地址的内存内容是危险的,例如:

int *p; 
*p = 10;

这个程序片段并未初始化指针就指定值给*p,会造成不可预知的结果,最好为指针设定初值,如果指针一开始不存储任何地址,可设定初值为 0,例如:

int *p = 0;

在指针定义时,可以靠在名称旁边,也可以靠在关键字旁边,或者是置中,例如:

int *p1;
int* p2;
int * p3;

这三个定义方式都是可允许的,C 开发者比较倾向用第一个,因为可以避免以下的错误:

int* p1, p2;

这样的定义方式,初学者可能以为p2也是指针,但事实上并不是,以下的定义p1p2才都是指针:

int *p1, *p2;

有时候,只希望存储内存的地址,可以使用void*来定义指针,例如:

void *p;

void*类型的指针没有任何类型信息,只用来存储地址,不可以使用*运算符对void*类型指针提取值,而必须转型至对应的类型,例如:

#include <stdio.h>

int main(void) {
    int n = 10;
    void *p = &n ;

    // 下面这句不可行,void 类型指针不可取值 
    // printf("%d\n", *p);

    // 转型为int类型指针并指定给iptr 
    int *iptr = (int*) p;
    printf("%d\n", *iptr);

    return 0;
}

执行结果:

10

顺便来看一下const定义的变量,被const定义的变量一但被指定值,就不能再改变变量的值,虽然可以强制如下改变变量值的:

const int n = 10;
int *p = &n; //  warning: initialization discards 'const' qualifier from pointer target type
*p = 20;
printf("%d\n", n);

然而,gcc会产生警讯,执行程序会显示 20,如果不想该地址的值被改变,可以用const定义指针,例如:

const int n = 10;
const int *p = &n;
*p = 20; // error, assignment of read-only location

必须留意的是,上面的p并不是只读,若想要有只读的指针,必须使用指针常数,也就是一旦指定给指针值,就不能指定新的内存地址值给它,例如:

int x = 10;
int y = 20;
int* const p = &x;
p = &y;  // error,  assignment of read-only variable `p'

因此,若xyconst定义,对应的指针常数定义会是如下:

const int x = 10;
const int y = 20;
const int* const p = &x;
p = &y;  // error,  assignment of read-only variable `p'




展开阅读全文