C 和指针第 6 章 指针 笔记

第六章标题就是指针,可见重要程度,接下来我就总结下各个小节的知识点。

内存和地址

这里说的内存其实是 虚拟内存,而不是 物理内存,两者通过 内存映射 来管理,也就是将 虚拟内存地址 映射到 物理内存地址
在这里插入图片描述
同时记住以下两点

1、内存中的每个位置由一个独一无二的地址标识
2、内存的每个位置都包含一个值

关于内存中的地址和内容,可以看下两张图
在这里插入图片描述
在这里插入图片描述
名字和内存位置之间的关联并不是硬件所提供的,它是由编译器为我们实现的。所有这些变量给了我们一种更方便的方法记住地址—硬件仍然通过地址访问内存位置。

值和类型

变量的值就是分配给该变量的内存位置所存储的值,即使是指针变量也不例外。

#include <stdio.h>

int main(void)
{
    int a=112, b=-1;
    float c=3.14;
    int *d = &a;
    int *f = &b;
    float *e = &c;

    printf("*d=%d, &d=%p, d=%p, &a=%p\n", *d, &d, d, &a);
    printf("*e=%f, &e=%p, e=%p, &c=%p\n", *e, &e, e, &c);
    printf("*f=%d, &f=%p, f=%p, &b=%p\n", *f, &f, f, &b);

    return 0;
}

运行打印

*d=112, &d=0x7fff40bd3b98, d=0x7fff40bd3bac, &a=0x7fff40bd3bac
*e=3.140000, &e=0x7fff40bd3b88, e=0x7fff40bd3ba4, &c=0x7fff40bd3ba4
*f=-1, &f=0x7fff40bd3b90, f=0x7fff40bd3ba8, &b=0x7fff40bd3ba8

可以看出指针存储的值就是变量的地址。

指针变量的内容

见上一节的代码及打印输出的值

间接访问操作符

* 叫做 间接访问 或是 解引用指针,后面跟着指针变量。

在这里插入图片描述

还是看 值和类型 给出的代码,指针 d 指向 a 的地址,那么 *d 表示对指针变量 d 进行间接访问操作得到值 112

未初始化和非法的指针

先来看个程序

#include <stdio.h>

int main(void)
{
    int *a;
    *a=112;
    return 0;
}

编译运行,提示 Segmentation fault。因为声明指针变量时,并未对其进行初始化,所以其值是未知的,而执行赋值操作时如果运气好可能不出错,但是下次动态分配内存的时候你可能就没这么幸运了。

其实用 GDB 调试时可以发现原因的,把程序命名为 seg.c

GNU gdb (GDB) 7.6.1
Copyright (C) 2013 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-unknown-linux-gnu".
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>...
Reading symbols from /var/www/html/test/c/0414/e...done.
(gdb) l
1	#include <stdio.h>
2	
3	int main(void)
4	{
5	    int *a;
6	    *a=112;
7	    return 0;
8	}
(gdb) b 5
Breakpoint 1 at 0x400478: file e.c, line 5.
(gdb) b 6
Note: breakpoint 1 also set at pc 0x400478.
Breakpoint 2 at 0x400478: file e.c, line 6.
(gdb) r
Starting program: /var/www/html/test/c/0414/e 

Breakpoint 1, main () at e.c:6
6	    *a=112;
(gdb) p a
$1 = (int *) 0x0
(gdb) p *a
Cannot access memory at address 0x0  ----------------> 这一步
(gdb) n

Program received signal SIGSEGV, Segmentation fault.
0x000000000040047c in main () at e.c:6
6	    *a=112;
(gdb) n

Program terminated with signal SIGSEGV, Segmentation fault.
The program no longer exists.
(gdb) n
The program is not being run.

NULL 指针

标准定义了 NULL 指针,执向 0x0 地址,这个地址没有访问权限,不要对这个指针进行解引用的操作,可以参照上一节 GDB 的调试信息。

指针、间接访问和左值

什么是左值,可以参考上一篇博客

左值 L-value 【意味着一个确定/特定的位置】
右值 R-value 【意味着一个值】
在使用右值的地方可以使用左值,但是在需要左值的地方却不能使用右值

间接访问操作符所需要的操作数是个右值,但这个操作符所产生的结果是个左值。

int a = 5;
int *d= &a;
*d = 10 - *d;

右边的间接访问作为右值使用,所以它的值是 d 所指向的位置所存储的值(a 的值)。左边的间接访问作为左值使用,所以 d 所指向的位置(a)把赋值符右侧的表达式的计算结果作为它的新值

d = 10 - *d;

这是个非法的值,因为右边产生个整型值,而左边是个整型的指针变量,类型不一致。

指针常量

此写法不常用。

*100 = 25;

这个赋值是错误的,因为 100 是整型字面量,而间接访问操作只能作用于指针类型表达式。

*(int *)100 = 25;

强制类型把值 100 从整型转换为指向整型的指针。如果 a 存储于位置 100,那么这条语句就是把 25 赋值给 a。

指针的指针

也就是二级指针,一般的一级指针,指针变量存储地址,而二级指针变量存储一级指针变量的地址,来看看下面的示例

#include <stdio.h>

int main(void)
{
    int a=12;
    int *b=&a;
    return 0;
}

GDB 调试

GNU gdb (GDB) 7.6.1
Copyright (C) 2013 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-unknown-linux-gnu".
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>...
Reading symbols from /var/www/html/test/c/0414/e...done.
(gdb) l  -----------------> 列出程序代码
1	#include <stdio.h>
2	
3	int main(void)
4	{
5	    int a=12;
6	    int *b=&a;
7	    return 0;
8	}
(gdb) b 5 -----------------> 在第五行也就是 int a=12; 打断点
Breakpoint 1 at 0x400478: file e.c, line 5.
(gdb) b 6 -----------------> 在第六行也就是 int *b=&a; 打断点
Breakpoint 2 at 0x40047f: file e.c, line 6.
(gdb) r -----------------> 执行程序
Starting program: /var/www/html/test/c/0414/e 

Breakpoint 1, main () at e.c:5
5	    int a=12;
(gdb) p &a  -----------------> 打印 a 的地址
$1 = (int *) 0x7fffffffe624
(gdb) n

Breakpoint 2, main () at e.c:6
6	    int *b=&a;
(gdb) p b -----------------> 打印 b 的值,这时还没初始化,默认为 (int *) 0x0
$2 = (int *) 0x0
(gdb) p &b -----------------> 打印 b 的地址
$3 = (int **) 0x7fffffffe628
(gdb) n
7	    return 0;
(gdb) p b -----------------> 打印 b 的值,和 a 的地址一样
$4 = (int *) 0x7fffffffe624
(gdb) p *b -----------------> 打印 *b 的值,和 a 的值一样
$5 = 12

指针表达式

这一节基本就是讲述指针表达式作为左值和右值的场景

char ch = 'a';
char *cp = &a;

以下场景都是基于上述代码展开

牢记两点:单独的指针变量即可做左值又可以做右值,对指针进行间接访问也是如此;但是对指针求地址以及求地址后 ++-- 都只能做右值,而不能做左值,因为位置是无法预知的,同时对指针进行间接访问后 ++-- 都只能做右值,而不能做左值,因为左值是地址,而这个结果地址是无法预知的。

在这里插入图片描述
在这里插入图片描述
再来看看非法的左值
在这里插入图片描述
在这里插入图片描述
接下来看看变种
在这里插入图片描述
在这里插入图片描述
++ 的优先级大于 *,并且遵循 自右向左 的结合律,看看上面两个表达式,发现最后都是对指针进行 接引用,加上括号后,就一目了然 *(++cp)*(cp++)

看看指针地址的变种 ++

在这里插入图片描述
在这里插入图片描述
其实可以看到,这两张图是对上面两张图里的表达式进行前缀 ++ 了,也就是 ++*(++cp)++*(cp++),这两个结果的地址都是无法预知的,因此都是非法的。

实例

第一个是求字符串的长度,这个很好求,以下代码为核心

while (*string++ != '\0')
	length++;

*string++,先 ++,再进行解引用,看看是否到了末尾,此时指针指向 +1 的位置,以此类推,循环结束后指针指向字符串后面的一个未定义位置

第二个实例是在二级指针中搜搜字符,这个要是学了数组后就很容易理解,这个放到数组那章做。

指针运算

1、算术运算只能做加减运算,而且必须是同一个数组内。
2、关系运算同 1<<=>>=,比较的是地址。

看看下面清除一个数组中所有元素的例子体会下

#define N_VALUES 5
float values[N_VALUES];
float *vp;

for (vp=&values[0]; vp<&values[N_VALUES];)
	*vp++=0;
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章