Linux C語言指針與內存

前面我們對於:

  • c語言的基本用法
  • makeFile文件的使用
  • main函數的詳解
  • 標準輸入輸出流以及錯誤流管道

工具與原理

指針與內存都是c語言中的要點與難點

  • 指針
  • 數組
  • 字元串
  • 堆內存與棧內存的差異
  • gdb內存調試工具

gdb是linux中的調試工具,可以讓我們直接查看內存中的數據。

我們可以看到cpu到底做了什麼事,而內存中又發生了什麼變化

C語言中指針的基本用法(初識指針)

main0.c:

#include <stdio.h>

void change(int a, int b)
{
int tmp =a;
a=b;
b=tmp;
}

int main()
{
int a=5;
int b=3;
change(a,b);
printf("num a =%d
num b =%d
",a,b);
return 0;
}

上述代碼無法實現a,b數值的交換。

改為指針類型實現代碼如下:

main1.c:

#include <stdio.h>
void change(int *a, int *b)
{
int tmp =*a;
*a=*b;
*b=tmp;
}

int main()
{
int a=5;
int b=3;
change(&a,&b);
printf("num a =%d
num b =%d
",a,b);
return 0;
}

為原來的變數值加上*, change函數改為傳入&a &b3和5可以成功的交換。

int* aint *a都是可以的,被稱為指針。& 取地址符。

我們要引入工具來分析

  • 需要將實參的地址傳到子函數才能改變實參! 如change(&a,&b)

C語言中int未初始化時,初值為隨機

int變數未初始化的默認初值,和變數的類型有關

  • 局部變數,在未初始化情況下,初值為隨機值。C規範對該初值並沒有做規定,具體實現由編譯器決定。如VC/VS等編譯器,會將初始值值為0xCCCCCCCC,而GCC等編譯器則是不可預知的隨機值。
  • 靜態局部變數,即帶static修飾的局部變數。全局變數和靜態全局變數,即定義在函數外,不屬於任何一個函數的變數。這幾種默認初值為0.

gdb工具的使用

通過gdb工具分析原理,分析結果

安裝gdb工具:

sudo apt install gdb
gdb -v

gdb可以單步調試,打斷點,查看內存中變數。但是即使生成了可調試版本,還是需要源代碼.c

  • gcc -g main0.c -o main0_debug.out生成可調試版本。
  • gdb ./main0_debug.out
  • l 全稱list:查看源代碼
  • 回車:繼續執行上條指令(此時的上條指令為l)
  • break 行數:設置斷點
  • start :單步調試
  • p a 全稱print:查看a在內存中的情況
  • n:執行到下一條語句

$1 $2 只表明是第幾個變數。正在顯示的這行是待執行。

我們想看change函數裡面是啥?而不是直接執行完函數。

  • s:進入函數內部

可以看到只是把數字傳進去了。

  • bt:查看函數堆棧(可以看到main函數和change函數)
  • f 1:切換到1號函數(棧頂的是我們當前所在函數)
  • q:退出調試

形參與實參,函數默認傳入變數其實只是將數值傳入,而函數內部的局部變數不會改變全局中的數值。change中的形參a,b只是個代號而已。

使用gdb調試帶指針的版本

此時傳遞的是地址。正好相差四個位元組。

下節課會介紹計算機內存的分配,什麼是堆內存,什麼是棧內存,內存地址,指針變數的實質是什麼東西。

  • p *a

int *a時, p a列印出的是a的內存地址, p *a列印的是這個地址里對應的值.P &a顯示a的內存地址空間

  • P &functionname: p + &函數名, 顯示函數程序在代碼段的內存地址

*a a這個地址的內容
&a a這個變數的地址

因為不知道一個指針指向的數據有多大, 所以需要在聲明一個指針變數的時候需要明確的類型。

不能交換數值的解析:只是傳值,只是change的局部變數,是實參的備份。

可以交換數值的解析加:變數加個指針,change傳入取地址符,實現交換功能。

計算機中數據表示方法:

計算機內存中最小的單位叫做位元組(Byte)

一個位元組是八個二進位位

為什麼是二進位呢?

因為我們的計算機是電子計算機,電流只有兩個狀態: 高電位(亮) 低電位(不亮)

人類習慣於十進位數字,可以將二進位與十進位進行轉換。(十個手指頭)

十進位滿十進一,二進位滿二進一

二進位寫起來太長了,為了方便我們顯示。

0x表示十六進位(滿16進1 ABCDEF)

1個16進位的數字,就可以表示4位二進位數字

  • 計算用二進位
  • 顯示用十進位
  • 編程用16進位

內存管理

  • 內存是什麼?

計算機系統中內存是由操作系統來統一管理的,一個位元組有八個bit,也就是八個二進位位。

不管插幾個內存條,都會把內存看成一個整體來計算內存大小。可是內存也不是你想插多少就插多少的。

32位的操作系統最大只能使用4G的內存

  • 那麼問題來了,為什麼32位操作系統只能使用4G內存呢?

因為32位的硬體平台上,cpu的地址匯流排是32位,也就是操作系統的定址空間是32位。

32位指的是: 給內存編號只能編到32個二進位位(這個編號就類似於我們街道的門牌號碼)

比如一個小區只有八棟樓,那麼這個編號就不能超過8.

cpu的地址匯流排有多少根,那麼編號也就只能有多少個組合。

因為地址匯流排可以存在多種狀態。

32根地址匯流排就有2的32次方個狀態

其中的一個編號就可以代表一個(內存的最小存儲單位)位元組。

所以一共可以存儲2的32次方個位元組。

  • 那麼問題來了2的32次方個位元組等於多少呢?

1024個位元組等於1KB 1024個KB等於1MB,1024個MB等於1GB

內存分配:

1byte = 8bit(1位元組 = 8進位位)

4G內存遠遠不夠用。(64位操作系統出現)

GB T PB EB

操作系統會對所有內存進行編號。每個號碼錶示一個唯一的位元組存放地址,一個位元組可以存放8個二進位位的數據。

所以64位操作系統內存地址編號

一共64個零到64個一

左側便是我們的計算機中內存的編號示意圖,從16位的0到16位的16個f。右側則是我們每個編號對應的內存,每個位元組(byte)可以保存8個bit(狀態位)

這些內存全都要交給操作系統來管理。因為我們的一個計算機中可能同時要運行多個程序。

多個程序由不同的人或團隊來開發,如果要由程序員來進行內存直接的管理是不太合理的。

多個程序對同一個內存地址來進行操作的話,到底分給哪個程序呢?這會引起衝突。

內存的佔用不確定, 不需要程序員來自己管理內存

應用程序是由操作系統來調用的

main()函數就是所有函數的入口,操作系統知道入口後就能執行代碼了,程序就可以被調用了。

操作系統: 除了能給內存做編號以外,還可以給內存做一定的規劃

比如在64位操作系統中: 程序員可以使用的內存只要有前面的48位就可以了。

也就是0X7fffffffffffffff(0x7fffffffffff = 01111111111111111111111111111111111111111111111)以下的。

而以上的內存空間是給操作系統內核使用的。

  • 用戶內存和操作系統內存隔離開的好處:

操作系統的內存不會被大量佔用,避免機器卡住,卡死,死機等狀態。

可通過操作系統把應用程序關閉,使得操作系統更安全。

作為用戶程序的內存空間又可以進行分段,從高到低又劃分為:、

  • 系統內核(亂入,屬用戶程序內存之外)
  • 棧(暫時存儲首先執行的程序狀態)
  • 自由可分配內存(可動態分配內存)
  • 數據段(聲明一些全局變數或者聲明一些常量)
  • 代碼段(程序源代碼編譯後存放在此)

我們寫的c語言代碼,編寫的函數在編譯後存到磁碟,運行程序時,就把源代碼編譯後的二進位數據載入到內存中。將源代碼編譯之後的二進位就會被存放在代碼段

聲明的全局變數或常量放置在數據段。

數據段的內存地址編號通常會大於代碼段。

高位內存空間分配給操作系統內核使用,低位內存空間分配給用戶程序使用。

每次調用新的函數,就將新的函數壓入棧區,正在調用的函數將位於棧頂。

64位系統中 只有前48位是給程序員使用的。 0x7fffffffffffffff ~ 0x0

劇透: 下一節中看在應用程序中棧,堆,數據段,代碼段的作用。

變數與指針的本質

簡單的例子:

#include <stdio.h>

int global = 0;

int rect(int a,int b)
{
static int count=0;
count++;
global++;
int s=a*b;
return s;
}

int quadrate(int a)
{
static int count=0;
count++;
global++;
int s = rect(a,a);
return s;
}

int main()
{
int a=3;
int b=4;
int *pa =&a;
int *pb =&b;
int *pglobal =&global;
int (*pquadrate)(int a)= &quadrate;
int s = quadrate(a);
printf("%d
",s);
}

rect求長方形面積,quadrate求正方形面積(內部實際調用了求長方形面積)。

為了方面我們查看內存的情況,添加了個業務邏輯無關的一些變數。

int global = 0;

全局變數global

static int count=0;
count++;
global++;

函數內的靜態變數: count,每個函數調用內部都讓count和global加加。

main函數中聲明了一系列的指針。

int *pa =&a;
int *pb =&b;
int *pglobal =&global;
int (*pquadrate)(int a)= &quadrate;
gcc -g main.c -o main.out //加-g生成的main.out才可以用gdb進行調試
gdb ./main.out //調試

gdb調試命令:

  • l(list) 列出代碼
  • start 開始調試
  • n 單步調試
  • s 進入函數內部
  • p 變數名 輸出變數的值
  • bt 查看棧標號
  • f 棧標號 切換棧
  • q 退出gdb
  • 回車 重複執行上一次的命令

之所以可以調試代碼?是機器碼被載入進了我們的內存(代碼段,它位於整個內存空間的最低位)

每一行都是我們的一條指令,被存放在代碼段。但是c語言的語法是不允許我們直接操作代碼段的。

除了代碼編譯後會存在代碼段以外,還有一個地方保存我們當前程序運行的狀態,比如當前在調用哪個函數,當前調用的函數運行到多少行?並且這個函數中有哪些變數,這些變數的值是什麼

就像是一張照相機拍攝的快照,記錄當前的狀態信息。這些信息會被記錄到棧內存中。

可以列印出當前的變數值,因為這個信息被記錄在了棧內存當中。

因為還沒有運行int *pa = &a; 內存中的pa值為空。

a裡面是3,b裡面是4,都被棧內存記錄下來了。

變數的本質是什麼?

  • 變數名只是一個代號(一個標識符)
  • 變數的本質就是內存

指針的本質?

指針pa也是一個變數,它也有自己的內存地址(0x7fffffffdcc8)。而這個內存地址中保存的數據是內存地址(0x7fffffffdcc0)。

C語言中所有的變數都有類型。

  • 指針保存的就是內存的地址。
  • 變數: a = 第五個柜子第二個抽屜。

操作系統對於內存的管理

我們可以看到操作系統是如何管理內存的,以及gcc這類編譯器對於我們源代碼所做的優化。

代碼段在整個內存地址中編號最小。

可以看出rect的編號小於quadrate的。代碼段中保存我們編譯之後的機器碼。計算機在執行的時候rect函數先被載入進去。quadrate函數後被載入進去。先載入進去的內存地址就更小一些。

因為這兩個函數是順序執行的,多以使用大的減小的就是rect在內存中佔用的大小。

數據段: 全局變數 & 常量都在我們的數據段當中。

可以看到數據段的地址是要比代碼段大的。

一個函數可以被多次調用,main函數可以被操作系統多次調用。

如我們多開qq

我們連續聲明了兩個變數,為何兩個變數ab的地址不連續呢?

因為這裡的地址指的是內存的首地址,如int佔四個位元組。那麼dcbc dcbd dcde dcbf都是屬於變數a的內存空間。那麼下一個內存地址的首地址就是dcc0了。

我們的b的首地址是dcc0,它也是int類型四個位元組,為啥下一個變數pa的地址不是dcc4而是dcc8呢?

這裡就涉及到我們編譯器的優化了,它為了讓cpu操作指令更快,提升程序的執行效率會對我們的源代碼做一定的優化。編譯之後的指令存儲有可能和我們編寫代碼的順序不一樣。

在代碼中我們還聲明了另一個整數類型變數s

int s = quadrate(a);

可以看到同樣為整數類型的變數s的地址與前兩個a,b是連續的。

gcc編譯器的優化,如果我們的函數中聲明了若干個整型變數,若干個指針類型變數,若干個浮點型變數,它會把我們的同一類型的變數聲明放到一起。接下來才聲明指針變數。

這樣的好處: 講到數組,指針計算的時候,解釋它的好處。

32位系統指針佔用4個位元組, 也就是32個bit, 64位系統佔用64個bit, 也就是8位元組。

可以看到指針pa的內存佔用從dcc8 到 dcd0(共8個位元組)pb從dcd0到dcd8,(共八個位元組),不管指針指向什麼,它本身內存中存放的都是內存地址,佔八個位元組。

int (*pquadrate)(int a)= &quadrate;

由dcd8 加上8個位元組。來到了dce0

代碼段中內存地址越來越大,先聲明的函數地址小,後聲明的函數地址大。

棧先聲明的地址大,後聲明的地址小,與代碼段數據段相反。

下一節中: main函數調用正方形,正方形調用長方形。搞清棧內存如何分配的,再搞清靜態變數,局部變數都是怎麼存放的,理解函數的返回值return。

函數棧以及數據段內存

進行再一次的調試。

運行到函數quadrate時,將a=3傳入。

可以看到棧中最下面的內存地址是最先分配的,如果從內存地址的大小來體現的話,main函數的內存地址大小第比較大的。

可以看到最先調用的main函數在最下面,然後是第二個調用的quadrate函數,最上面永遠是當前執行的函數。

棧的特點: 先進後出。 最後進的是rect函數,最先出去的也應該是rect函數。

可以看到越到棧頂的函數,兩個s(第一個s是存放棧頂的rect函數返回值的,第二個s是存放quadrate函數返回值的)

0x7fffffffdc74 0x7fffffffdc9c 棧頂的更小一點,也可以體現出越晚進來的,地址越小。

可以看到更早進來的main函數中s地址更大。因此棧中是越早進來越在棧底,地址越大。

可以看出我們在rect中的靜態變數count,和我們在quadrate中的靜態變數count是連續存儲的。

static int count=0;

可以看出函數內的兩個靜態局部變數count是獨立的,連續存儲的。而兩個函數中的global變數都是指向同一個地址的。

觀察大小,我們局部靜態變數count的地址值和去全局變數的地址值都很小。因此說明他們並不存放在棧中。(棧的地址很大)

我們的靜態變數,常量,包括全局變數,默認都存儲在數據段中。由於靜態變數時屬於某個函數特有的,所以靜態變數也是屬於某個函數特定的,是獨立的。全局變數是所有函數公用的,但是由於他們都在數據段中,即使一個函數被多次調用,靜態變數指向的還是數據段中的一個固定地址。不同函數里的count是不同的count,但是同一個函數不管調用多少次,這個count都指向同一塊內存。

數據段(data segment)通常是指用來存放程序中已初始化的全局變數的一塊內存區域。數據段屬於靜態內存分配。

編譯器優化代碼,把聲明時不在一起的同一類型變數,放到一起(某種程度上修改了源碼)

如聲明

int a; float b; int c;

編譯後變數a的地址和c的地址是連在一起的.CPU在編譯的時候對棧內變數的存儲地址進行優化,他會將類型相同的變數在連續地址中儲存。

地址分配: 代碼段,數據段是從下往上分配(先低地址,後高地址)。棧是從上往下分配(先高地址,後低地址)

函數中靜態變數,局部變數區別:

局部變數(相對數據段而言的高地址)中,而靜態變數數據段(低地址)中.

所以在多次調用函數時,靜態變數不會被重新初始化.或者這麼說,靜態變數的生存周期和數據段相同,局部變數生存時間受調用函數時,所屬函數進棧出棧的影響而會重新初始化.

全局變數和靜態變數都在數據段中,但靜態變數是某個函數特有的.

下面來探究函數指針是怎麼一回事?

指針可以指向一個變數,吧變數的值取出來。函數指針?

(函數指針與指針指向的數據訪問)

修改我們的源代碼(上面我用的是修改過的,但是不影響上面概念的理解)

int s = quadrate(a);

修改為:

// int s = quadrate(a);
int s = (*pquadrate)(a);

函數指針在調用的時候傳入a的值進來。

gcc -g main.c -o main.out //加-g生成的main.out才可以用gdb進行調試
gdb ./main.out //調試
int (*pquadrate)(int a)= &quadrate;

這一行是一個函數指針

這裡依然可以進入函數內部,運行代碼時函數指針也可以調用函數內容。這種做法經常用於寫程序時做回調函數使用。

  • p &a 是將變數a的內存地址找出來。&符號時取地址符

  • 如果已經知道一個地址如何取裡面的數據?

  • p *&a: 取變數a所在地址的值 (先進行&運算,&a相當於取變數a的地址,在執行*運算,*&p相當於取變數a所在地址的值)
  • p &*a:取變數a的地址 (先進行*運算,*a相當於變數a的值,再進行&運算,&*p就相當於取變數a的地址)

quadrate 本身是一個函數指針,*quadrate取出了指向的函數內容(一組指令構成),(*quadrate)(3)就表示調用函數,並傳入參數3

  • p pa pa是指向pa的地址。
  • p *pa 代表取出0x7fffffffdcbc這個地址存放的值,pa指向的是一個棧的地址,如果是棧內存地址肯定是要訪問數據,棧,堆,數據段內存都認為是取數據。代碼段是找到一個代碼塊。

  • p &pa 指找到pa變數本身的地址。

下面: 數組,動態堆內存創建,指針運算。

數組申明的內存排列

示例代碼:

#include <stdio.h>
int main()
{
int a =3;
int b =2;
int array[3];
array[0] =1;
array[1] =10;
array[2] =100;
int *p=&a;
int i;
for (i = 0; i < 6; i++)
{
printf("*p=%d
",*p);
p++;
}
printf("-------------------------------------
");
p =&a;
for (i = 0; i < 6; i++)
{
printf("p[%d]=%d
",i,p[i] );
}
return 0;
}

為了說明問題,所有的數據類型統統是整型,除了指針p以外。c語言的數組類型是比較原始的,在函數內聲明,因此也在棧內存當中。指針p指向a的地址。

p是一個指針,指針的加加操作。我們類比一下,整數類型的加加,3++,下次列印就會變成4。

p[i] 指針的括弧取值,與數組取值有些類似。

gcc -g main.c -o main.out //加-g生成的main.out才可以用gdb進行調試
./main.out //觀察結果
gdb ./main.out //調試

for循環括弧里加不加int,內存中還有區別的。

指針p指向的值等於3,1,2

int類型的內存地址和數組內存地址不連續,而是差了16位。

for (i = 0; i < 6; i++)
{
printf("*p=%d
",*p );
if(i == 2){
p=p+4;
}
else{
p++;
}
}

將第一個for循環中的代碼改為如上面所示。

for(i = 0; i < 6; i++)
{
if(i > 2){
printf("p[%d]=%d
",i+3,p[i+3] );
}else{
printf("p[%d]=%d
",i,p[i] );
}
}

此時我們列印a的地址,列印p的地址是一樣的。因為我們把a的地址賦值給了p

每個整型數字佔四個位元組。

(gdb) p *p
$3 = 3
(gdb) p *&a
$4 = 3
(gdb) p *0x7fffffffdcc4
$5 = 3
(gdb) p * 0x7fffffffdcc4
$6 = 3

可以看到四種等價的操作。都是*加上地址,可以直接列印出內存中的數據值。

先聲明了a,再聲明了b。但是我們a的下一個內存地址中存放的卻不是b。c8地址中存放的是0

gcc編譯器有自動優化功能會把所有的同一類型的變數放到一起來聲明。因為我們還聲明過一個i變數,i也是整型的。

會把i也和a,b放在一起,具體哪個變數在前,哪個變數在後。

通常情況先寫的會在前面,這裡因為我們i在很下面聲明的,中間又隔了一個指針p

0的值就等於i的值,兩個指向同一個地址。

main函數執行的棧中,最低的地址放的a的值,接下來是i的值,i的值之後應該推測是b的值。

0x7fffffffdcc8 + 4
0x7fffffffdccc

可以看出地址順序依次增大: a i b

一直p p p的輸出很麻煩,如何方便的輸出?

x/3d 0x7fffffffdcc4

x表示要輸出內存中的值,/表示要輸出幾個值,輸出3個值。按照什麼類型來輸出。d按照十進位進行輸出。從哪個地址開始顯示呢?

我們還可以指定顯示變數要有多大長度,默認是4個位元組。

取九塊內存地址的內容,可以看到整數類型的數字和數組的存儲中間相差三個內存空間(地址上從頭到頭,相差16個位元組)

那些隨機值是不可控的,程序中使用到未初始化的值,會對軟體造成異常。

因為c語言不做指針的安全檢查,它會操作這個地址的值等,未初始化,有可能是其他程序使用過的值。

棧內存中, 連續的地址空間來存放整型變數和我們的數組元素。

可以看出數組是按順序放置元素的。

指針運算。

可以看到指針往下移動了4格,可是指針怎麼知道要加四格呢?

因為程序員在聲明指針類型的時候是整型,int佔四個位元組。所以p++的時候會一次移動4個。

這是指針的偏移運算。指針的偏移運行效率高,性能好。

p +=3;

把指針往下移三格(整數類型指針)移動12個位元組

*p =101;

將p指針所指向的值修改為101

p =&a;

讓p再次指向a的地址,不影響我們下面的列印。

可以看到原本p指向a,然後往下移動三格(忽略整型與數組中間三塊內存,四個地址差)

第一次,從a移動到i;第二次,從i移動到b;第三次,從b移動到數組第一個元素。

p[3] //等價於p +=3,也就是把p往下移動三格
*p = 101
//上面兩行合二為一的想法是錯的,因為只有下面這行才能起到理想目的。
p[3] = 101
int *p=&a;
p[2];
*p = 66;

P[4]不是p往下面移動了4個位置,而是從p開始的地址往後移動4個位置取值,p指向的地址還是不變的這時候就不用跟採用p++時,再將指針歸位了。

int array[2];
int *pa =array;
pa[0]=1;
pa[1]=10;
pa[2]=100;

如果說數組本身也是一種指針類型的話,裡面就是地址。把地址賦給地址變數就不需要加取地址符了。

任何需要用數組操作的地方,都可以用指針來代替。因為我們的指針變數本質上是內存地址,數組也是地址。

反過來就不行了,指針能做的,數組不一定能做。

int array[2];
array+=2;

上面的代碼就是錯誤的。

數組其實就是個指針常量,指針是指針變數,常量是不可更改的。array永遠都指向的是同一個地址,當然地址裡面的內容是可以改變的。

下節課: 一種特殊的數組,字元數組

字元數組和指針字元串

小示例代碼:

#include <stdio.h>

int main()
{
char str[]="hello";
char *str2="world";
char str3[10];
printf("input the value
");
scanf("%s",str3);
printf("str is %s
",str);
printf("str2 is %s
",str2);
printf("str3 is %s
",str3);
}

聲明了一個字元數組並賦值hello,又聲明了一個字元指針,還聲明了一個長度為10的字元數組,並未初始化。

通過scanf將輸入的字元串寫入str3中。

然後進行列印。

gcc -g main.c -o main.out

生成可調試代碼。

gdb main.out //開始調試

列印str和str2的時候,都可以列印出內存中的字元串來。

str直接列印出裡面的值,因為str2指明的是指針類型,會等於一個地址0x5555555548b4

地址是一個很小的地址(相對於0x7),是在代碼段中的地址,是源代碼編譯就編譯進去的。 而我們的str2隻是指向這個地址而已。

可以看出str2這個指針對應的是整個數組的首地址而已。首地址對應的內存中存儲著首字母w

119是w對應的ASCII碼。第6個值,是因為上面o的那次,已經將指針後移了一位,++後移第二位。

指針忘記歸位,導致str2隻剩下三個字母。

字元類型的指針和字元數組也是可以混用的。

一般的我們通過scanf輸入,是要輸入&a,也就是要加取地址符的。

聲明str3的時候,它是一個字元數組,數組就是內存地址。str3就可以直接傳進去,不需要取地址符。

scanf("%s",str);

將輸入存放至str中。

可以看到因為數組的本質是指針常量,str指向的地址,被mtianyan2str填充。而str3的指針還指向原來的位置,所以造成str3的內容為str的後半部分。

str在創建時有五個字母+一個null結束符,但是它的數組長度是6。

所以計算str3時需要減去6個字母才可以得到。

char str4[]={h,e,l,l,o};
int len = sizeof(str4) / sizeof(char);

而採用這種單字母初始化方法,數組長度與字元個數一致。

我們嘗試向str2中寫入東西。

可以看到往str2寫數據,會出現段錯誤(核心已轉儲的錯誤)

c語言的字元串是一個字元數組,以/0結尾。有五個字元就有六個長度

gdb的x命令,可以列印地址中的值

- x/個數 地址- x/6cb 地址: 列印該地址後的6個字元,c:字元形式列印,b:列印的單位按byte

scanf可以將輸入存入str或str3,但是不能存入str2

堆和棧內存里才可以寫入(預留空間才可寫入),而str2是編譯之後,載入到內存的一個代碼段變數,不允許寫入。操作系統對內存做安全管理。

我們聲明一個函數把一個函數定義好了,函數所在的棧的內存就分配好了。

而我們使用malloc函數,它會為我們分配堆內存。

字元數組的深入理解

示例代碼2:

#include <stdio.h>

int main()
{
char str[]="hello";
char *str2="world";
char str3[10];
printf("input the value
");
str[3]=

相关文章