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;
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章