Linux C 编程 - C 语言指针与数组

  • 基本概念

先看以下例子:

int a[10];
int *pa = &a[10];
pa++;

首先指针pa指向a[0]的地址,注意后缀运算符的优先级高于单目运算符,所以是取a[0]的地址,而不是取a的地址。然后pa++让pa指向下一个元素(也就是a[1]), 由于pa 是int* 指针,一个int 型元素占4个字节,所以pa++使pa 所指向的地址加4,注意不是加1。

用方框表示存储空间,用箭头表示指针和变量之间的关系:

既然指针可以用++运算符,当然也可以用+, - 运算符,pa指向a[1],那么pa+2指向a[3]。

*(pa+2)也可以写成pa[2],pa就像数组名一样,a[2]之所以能取数组的第二个元素,是因为它等于*(a+2),当数组名做右值时自动转换成指向首元素的指针,所以a[2]和pa[2]本质上是一样的,都是通过指针间接寻址访问元素。

由于a做右值使用时和&a[0] 是一个意思,所以 int *pa = &a[0]; 通常不这么写,
而是写成更简洁的形式: int *pa = a;

在函数原型中,如果参数是数组,则等价于参数是指针的形式,例如:

void func(int a[10])
{
  	...
}

第一种形式的方括号中的数字可以不写,仍然是等价的:

void func(int a[])
{
 		... 
}

参数写成指针形式还是数组形式对编译器来说没有区别,都表示这个参数是指针,之所以规定两种形式是为了给代码的人提供有用的信息,如果这个参数指向一个元素,通常写成指针的形式,如果这个参数指向一串元素中的首元素,则通常写成数组的形式。

  • 指针与自增运算符之间的关系

(*p)++, 先传值,后值自增1,类比a++

*p++ == *(p++), 先传值,后地址自增1

++*p == ++(*p), 值先自增1,后传值,类比a++

*++p == *(++p), 地址先自增1, 后传值

#include <stdio.h>

int main()
{
    int a[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
    int *pa = &a[0];
    printf("*pa = %d\n", *pa);
    pa++;
    printf("*pa = %d\n", *pa);
  	printf("*pa = %d\n", *(++pa));
    return 0;
}
  • 指针与const限定符

const限定符和指针结合起来常见的情况有以下几种:

const int *a;
int const *a;

这两种写法是一样的,a是一个指向const int 型的指针,a 所指向的内存单元不可改写,所以(*a)++是不允许的,但 a 可以改写,所以a++是允许的。

int * const a;

a 是一个指向int 型const 指针,*a 是可以改写的,但a不允许改写。

int const * const a;

a 是一个指向const int 型的const 指针,因此*a 和 a 都不允许改写。

指向非const 变量的指针或者非const变量的地址可以传给指向const变量的指针,编译器可以做隐式类型转换,例如:

char c = 'a';
const char *pc = &c;

但是,指向const变量的指针或者const变量的地址不可以传给指向非const变量的指针,以免透过后者意外改写了前者所指向的内存单元,下面的代码编译器在编译时会报警告:

const char c = 'a';
char *pc = &c;

良好的编程习惯应该尽可能多地使用const, 有以下几个优点:

1. const 给代码的人传达非常有用的信息。比如一个函数的参数是 const char *, 或者是const int *,
在调用这个函数时就可以放心地传给它char * 或 const char * 指针,
而不必担心指针所指的内存单元被改写。

2. 尽可能多地使用const 限定符,把不该变的都声明为只读,这样可以依靠编译器检查程序中的Bug,
 防止意外改写数据。

3. const 对编译器优化是一个有用的提示,编译器也许会把const 变量优化成常量。

字符串字面值通常分配在.rodata段,字符串字面值类似于数组名,当字面值作为右值使用时自动转换成指向首元素的指针,这种指针应该是co nst char * 型。printf 函数原型的第一个参数是const char *型,可以把char *或const char *指针传给它,参考下面的调用:

const char *p = "abcd";
const char str1[5] = "abcd";
char str2[5] = "abcd";
printf(p);
printf(str1);
printf(str2);
printf("abcd");

如果要定义一个指针指向字符串字面值,这个指针应该是const char * 型,如果写成char *p = "abcd"; 会有被串改的隐患:

int main(void)
{
         char *p = "abcd";
         ...
         *p = 'A';
         ...
}

p 指向 .rodata 段,不允许改写,但编译器不会保存,在运行时出现段错误。

举报
评论 0