?指針(1)學習流程
————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————?
內存和地址
內存
所謂的指針就是為了管理內存空間,劃分的一個內存一個單元。
內存單元是計算機內存中的最小存儲單位,通常以字節(Byte)為單位。每個內存單元都有一個獨特的地址,以便CPU可以準確地找到并讀寫數據。
指針在C語言或其他允許指針操作的編程語言中非常重要,因為它們提供了直接訪問和操作內存的能力。使用指針,程序可以避免數據復制,從而提高效率,尤其是在處理大型數據結構(如數組和復雜的數據結構)時。
地址?
內存單元的編號就是地址 就是指針
首先,必須理解,計算機內是有很多的硬件單元,而硬件單元是要互相協同工作的。所謂的協同,至少相互之間要能夠進行數據傳遞。
但是硬件與硬件之間是互相獨立的,那么如何通信呢?答案很簡單,用"線"連起來
而CPU和內存之間也是有大量的數據交互的,所以,兩者必須也用線連起來。
CPU訪問內存中的某個字節空間,必須知道這個字節空間在內存的什么位置,而因為內存中字節很多,所以需要給內存進行編址(就如同宿舍很多,需要給宿舍編號樣)。
計算機中的編址,并不是把每個字節的地址記錄下來而是通過硬件設計完成的。
鋼琴、吉他 上面沒有寫上“剁、來、咪、發、唆、拉、西”這樣的信息,但演奏者照樣能夠準確找到每一個琴弦的每一個位置,這是為何?因為制造商已經在樂器硬件層面上設計好了,并且所有的演奏者都知道。本質是一種約定出來的共識!
硬件編址也是如此
我們可以簡單理解,32位機器有32根地址總線,每根線只有兩態,表示0,1【電脈沖有無】,那么一根線,就能表示2種含義,2根線就能表示4種含義,依次類推。32根地址線,就能表示2^32種含義,每一種含義都代表一個地址。
地址信息被下達給內存,在內存上,就可以找到該地址對應的數據,將數據在通過數據總線傳入CPU內寄存器。
cpu內部寄存器地址線和數據線的布局。具體來說,在cpu的左邊有8個地址線,從0到7,用于指定內存中的地址。每個地址線對應一個數據線,用于讀寫內存的數據。此外,還有一個控制總線(R/W),用于控制數據的讀寫操作。在cpu的右邊是內核(Kernel)部分,它與地址線和數據線相連,用于接收來自內存的數據或指令。
通過物理線傳到某一個過程 訪問那個數據的內存
這里也有物理地址和虛擬地址
————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
指針變量和地址
?指針變量是什么
1.指針是內存單元中一個最小的單元編號,也就是地址
2.平時口語說的指針,通常指的是指針變量,是用來存放內存地址的變量
總結:指針就是地址,口語中說的指針通常指的是指針變量,是用來存放內存地址的變量。
?地址大小決定指針變量的大小
這里我們就明白:
在32位的機器上,地址是32個0或者1組成二進制序列,那地址就得用4個字節的空間來存儲,所以.
一個指針變量的大小就應該是4個字節。
那如果在64位機器上,如果有64個地址線,那一個指針變量的大小是8個字節,才能存放一個地址
總結:
指針變量是用來存放地址的,地址是唯一標示一個內存單元的,指針的大小在32位平臺是4個字節,在64位平臺是8個字節。?
指針變量的舉例
這里我們需要知道創建了變量的本質是申請一塊空間
當然這里可以看見,在系統申請空間的時候,是隨機給一個地址的
也就是
在C語言中,變量的內存分配通常是在程序的堆棧(stack)或堆(heap)上進行的。堆棧通常用于存儲局部變量,而堆則用于存儲動態分配的內存。當程序執行到聲明變量的語句時,系統會為該變量分配一塊內存空間,并將其地址賦給變量。
關于地址的選擇,現代操作系統和編譯器通常會使用一種稱為“地址空間布局隨機化”(Address Space Layout Randomization, ASLR)的技術來提高安全性。ASLR會確保每個進程的內存地址空間是隨機的,這樣就增加了攻擊者利用內存漏洞的難度。因此,每次程序運行時,即使它申請的內存空間大小相同,分配的內存地址也可能不同。
這里引入
&單目操作符(取地址操作符)
#define _CRT_SECURE_NO_WARNINGS 1
int main()
{int a = 100;int arr[10];return 0;
}
?%p打印地址
&單目操作符(取地址操作符)
取地址 取出來的是較小的地址 當知道較小的地址后 順藤摸瓜就找到了
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
int main()
{int a = 100;int arr[10];printf("%p\n", &a);return 0;
}
?此時p也就是一個指針變量
當然 此時的語法還不對 但是此時取出的a的地址 給到了p 此時p也就是我們口中說的指針變量
這里平時說的p是指針 其實說的p是指針變量
取出來的地址 放到p里面 所以p是指針 也就是指針變量
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
int main()
{int a = 100;int *p = &a;//int arr[10];//printf("%p\n", &a);return 0;
}
p是指向a的
因為p可以找到a
所以p是指向a的
*說明是指針變量 int 說明p指向的對象是int類型的
指針變量就是存放地址所以引用指針變量
任何東西只要放到指針里面 就唄指針認為是指針變量
這里不僅僅只是為了存放地址
此時p可以很好的找到a的地址
此時*p叫解引用操作符 或者間接訪問操作符
這里需要知道
對p解引用 找到的結果找到的是a
取出a的地址 解引用 然后此時a就等于賦值的數值
關于指針變量大小
等同于找老莫我想吃魚一樣 老大不方便動手 小弟動手
地址是由地址線傳入 一個地址線產生一個二進制位數 32個地址線產生2的32次方位地址線
所以在平臺上面 x86的環境下面
一個指針的大小需要四個字節
所以地址長度決定指針變量的長度
所以64位就是一個指針就是8個字節
舉例 32位的情況下 都是四個字節 和類型無關
這里插入兩個內容 關于環境和計算 有利于指針的學習
計算機基礎( 計算機里面為什么是32位指的是什么)(C語言代碼舉例)(畫圖超詳細解析)_c語言中32位是什么意思-CSDN博客
計算機基礎知識講解(原碼反碼補碼)(以及在C語言里面是如何計算和運用的)-CSDN博客?
這里需要知道x86 是32位的環境
至于是為什么
"x86"這個名稱來源于Intel在1978年推出的8086處理器,它是第一個真正意義上的16位微處理器。后來,Intel和AMD等公司推出了基于8086架構的擴展和改進型號,如80286、80386和80486等。這些處理器雖然技術上仍然是16位的,但它們引入了擴展寄存器和指令,使得它們能夠處理32位數據。
由于這些處理器在市場上獲得了巨大成功,人們開始將它們統稱為"x86"架構。這個名稱并沒有明確指出它是16位還是32位,但實際上,從80386開始,"x86"架構就已經支持32位操作了。
因此,"x86"這個名稱并不是指32位環境,而是指一系列基于8086架構的處理器。不過,由于這些處理器在32位時代獲得了廣泛應用,人們往往用"x86"來指代32位環境。這種用法雖然不太準確,但在實際應用中卻是普遍接受的。
回歸正題
?x64 就是八個字節
這里需要知道
指針里面存方的一般是16進制
至于為什么是16進制 往往是因為16進制計算范圍較大 比較方便
但是需要知道的是
存放的二進制 顯示的16進制 簡單說就是方便
————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
指針類型
沒有解引用之前 a的地址確實給到pa
但是當解引用的時候只改變了第一個字節
說明 指針類型決定指針的權限
整形指針一次只能訪問四個字節
這就是指針類型的意義
如果你想訪問一個字節 char
如果是四個字節 用int
再多就是float等等
因為這里你需要知道,在初始化的時候,分配內存和數值是隨機分配的,你進行指針的訪問,也只是根據類型來進行訪問
指針類型的意義
?指針類型決定了指針在進行解引用操作符的時候訪問幾個字節,也就是決定指針的權限
加上數字 發現不一樣 int*和char*每次跳過的也不一樣
指針類型的計算
指針類型決定+1 -1的時候一次走的距離
指針類型決定向前或者向后走一步 多少距離
?簡單的說就就是,此時打印出來的地址,減去之前的地址是大四個字節的
從而引出指針類型的意義
根據上述,我們反過來講解指針類型的意義
從而引出指針類型的意義
此時是短整型char 前后差距是一個字節?
但是是int的情況下 前后差距就是四個字節
解引用不同的類型之間轉化
對于指針解引用不同的類型訪問不同的字節長度
取出的類型是一個整形的類型
但是放到了短整型里面
此時就會導致數據的丟失
void可以存放任何類型的地址 因為沒有具體類型
在C或C++等編程語言中,
`void` 指針是一種特殊的指針類型,它不代表任何具體的內存地址類型。
`void` 指針可以用來存放任何數據類型的地址,因為它是所有類型的基類型。
但是,需要注意的是,雖然 `void` 指針可以存儲任何類型的地址,
但是不能直接使用它來訪問或操作這些地址所指向的數據,因為編譯器不知道具體的數據類型,也就無法進行正確的類型匹配和內存布局。
在實際應用中,如果想要通過 `void` 指針訪問具體類型的數據,需要進行類型轉換,即將 `void` 指針強制轉換為相應的類型指針。
例如:
```c
int a = 10;
void* void_ptr = &a;
int* int_ptr = (int*)void_ptr;
*int_ptr = 20; // 現在 a 的值變為 20
```
在這個例子中,`void_ptr` 存儲了 `int` 類型變量 `a` 的地址,
但是不能直接通過 `void_ptr` 來修改 `a` 的值。我們通過類型轉換得到了 `int` 指針 `int_ptr`,
然后就可以通過 `int_ptr` 來修改 `a` 的值了。使用 `void` 指針時需要格外小心,
因為錯誤的類型轉換會導致未定義行為,比如內存訪問越界,這可能會導致程序崩潰或產生不可預測的結果。
因此,在使用 `void` 指針時,確保進行正確的類型轉換和內存管理是非常重要的。
所以在不確定是什么類型的時候 用void類型的接受
不過需要知道的是
void具有局限性 因為沒有具體類型
所以無法運算訪問字節
如圖
這里顯示的是未知的大小
————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
const
首先解釋一下const
?在C語言中,'const'用于聲明一個只讀的變量。這意味著該變量一旦被初始化,其值就不能被改變。常量通常用于表示某些固定的、不變的值,例如物理地址、尺寸或數學常數等。
在C語言中,`const` 是一個關鍵字,用于修飾變量,以表明它們的值在定義之后不能被改變。`const` 關鍵字具有以下兩個主要屬性:
1. 只讀屬性:被 `const` 修飾的變量在初始化之后,其值不能被修改。這意味著一旦你給一個 `const` 變量賦了一個值,你就不能再次給它賦一個新值。
2. 類型安全:`const` 關鍵字告訴編譯器該變量是一個常量,因此在程序執行期間,試圖修改這樣一個變量的值會導致編譯錯誤或運行時錯誤(取決于如何嘗試修改它)。
`const` 可以用在變量聲明的任何位置,具有不同的含義:
- `const` 在變量類型之前:表示變量是一個只讀的常量,不能被賦予初始值,必須在聲明時初始化,并且它的值在程序的整個運行期間都不可更改。
- `const` 在變量類型之后:表示變量本身是可變的,但其指向的數據是只讀的。這在指針中非常有用,可以確保指針指向的數據不被修改,但指針本身(即內存地址)可以改變。
例如:
```c
const int a = 10; // 聲明一個常整型變量,初始化為10,之后不能更改
int const b = 20; // 同上,這兩種聲明方式等效
int arr[10];
const int n = 5; // 聲明一個常整型變量,表示數組的大小
int* ptr = arr;
const int* const ptr_to_const = arr; // 聲明一個指向常量的常量指針
// ptr_to_const指向的arr數組元素是常量,不能被修改,同時ptr_to_const本身的值也不能被改變
```
使用 `const` 可以提高代碼的可讀性和可維護性,同時也幫助編譯器進行更好的優化。此外,`const` 關鍵字在C++中還有更多的用途,例如用來定義常量對象和成員函數,但在C語言中,它的使用主要局限于變量聲明。
簡單的說 ,可以片面的理解為,就是他存在的意義就是,防止新手修改老手的代碼的時候,不小心吧東西修改錯誤
const 本質
下面我會介紹什么時候回進行修改 以及范圍 當然在這之前先介紹完const本身的屬性
const 本質 是不能修改?
此時用const修飾a之后?
這里的a具有了長屬性 長屬性就是不能被修改的屬性
這里需要知道
雖然a不能被修改,但是本質還是變量 就是常變量
但是在c和c++里面const是不一樣d
在c里面 const修飾的是變量
在c++里面則是常量
所以寫C語言的話 后綴就要寫c?
但是這樣是不符合規則的
為什么是不合規的,前面已經說明 ,這里再詳細的說一下
`const int a = 10;` 聲明了一個常量 `a`,它的值不能被改變。接著,`int* p = &a;` 聲明了一個指向 `a` 的指針 `p`。
然后,`*p = 0;` 這行代碼試圖修改通過指針 `p` 所指向的內存位置的值。由于 `a` 是一個 `const` 變量,它的值不能被改變,所以這行代碼是不正確的,會導致編譯錯誤。
在 C 語言中,`const` 關鍵字的作用是確保變量在其生命周期內不被修改。嘗試修改一個 `const` 變量的值會導致編譯錯誤。因此,如果您嘗試執行 `*p = 0;`,編譯器會報錯,因為它不允許您通過指針 `p` 來修改 `a` 的值。
正確的做法是只讀取 `a` 的值,而不是嘗試修改它。例如:
```c
const int a = 10;
int* p = &a;
// 正確的使用方式是讀取a的值,而不是修改它
printf("%d\n", *p); // 輸出 10
```
如果想要修改 `a` 的值,需要首先取消 `const` 限制,或者使用另一個變量來存儲 `a` 的值,然后修改那個變量的值。例如:
```c
int a = 10;
int* p = &a;
*p = 0; // 正確,修改了a的值
```
或者:
```c
const int a = 10;
int b = a; // b 是一個普通變量,可以被修改
b = 0; // 正確,修改了b的值,而不是a
```
const的使用規則
?所以此時p指向的是a的地址
所以這個時候可以理解為 p本身就有一個地址 然后p指向了一個地址 p里面存放了a的地址
所以
p指向一個地址
p本身有一個地址
p存放一個地址
數值出來是100
const修飾指針變量的時候放到*的右邊 限制的是指針變量本身,指針變量不能指向其他變量 但是可以修改指針變量指向的內容
p不能修改 但是*p指向的內容可以進行修改
const放到*左邊
修飾指針變量的時候
此時限制的是*p
但是可以修改指針變量本身
也就是此時 指針指向的內容是不能發生變化的 但是指針(也就是指針變量本身是可以發生變化的)
const生動舉例
當const在*左邊的時候、,,const* ,,這里假設有一對男女朋友,這個有一天女孩說,我想吃雪糕需要五十塊 ,這個男孩說那我沒錢一共十塊錢,買不起,那女孩就不高興了,說:那你的錢(本身的變量)不發生變化的話,那我只能變化,換男朋友了。
當const在*右邊的時候、,,*const?,,這里假設還是這一對男女朋友,還是這個場景,這個有一天女孩說,我想吃雪糕需要五十塊 ,這個男孩說那我沒錢一共十塊錢,但是沒事我借錢40,(男孩這個指針指向的變量發生了變化),買了一個雪糕還有0元,那女孩就繼續跟著你了,也就是指針變量本身沒有發生變化
?
const在*左邊指針變量本身的地址變化 但是指向的內容不變化
所以這里就是你不給我花錢 指針女孩換男朋友m->n 女孩本身變化 男朋友的錢m數值不變化
此時m愿意花錢 然后等于把const放到右邊 等于m愿意花錢 女孩指針p本身不變化 不換男朋友,但是男朋友m的錢發生變化 也就是指針變量本身不發生變化,但是指向的變量可以發生變化
當然兩邊也都可以加上const數值
總結
想搞定變量*const int
想搞定指針變量本身 const * int
————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
assert斷言
這里先簡單講一下assert的斷言,下面會有assert斷言和野指針的結合使用?
測試和寫代碼直接轉換問題?
這里鏈接一下?relase和debue的區別關于Visual Studio Installer總是出現(LNK1168無法打開 )(不需要關機直接處理好)_vs installer打不開-CSDN博客
測試和寫代碼直接轉換問題
這里講一下為什么在這個測試和寫代碼直接轉換的時候會不一樣
C語言在測試和寫代碼時可能會出現不一樣的結果,這主要是由于以下幾個原因:
1. **浮點數精度問題**:C語言中的浮點數計算通常不會得到精確的結果,因為計算機內部用二進制表示浮點數時,無法精確表示所有的小數。所以在不同平臺或者不同編譯器上運行時,浮點數的運算結果可能會有細微的差異。
2. **Endianness(字節序)**:字節序是指計算機中字節存儲的順序。C語言中沒有明確指定字節序,這可能導致在不同硬件平臺上,多字節數據(如整數、浮點數)的存儲順序不同,從而影響數據的解析和運算。
3. **編譯器和運行時的優化**:編譯器在將C代碼編譯成機器碼時,可能會進行各種優化。這些優化可能會改變代碼的執行邏輯或者運算結果。另外,運行時的庫函數也可能對輸入數據進行不同的處理。
4. **測試環境和寫代碼的環境差異**:如果在不同的環境(如不同的操作系統、硬件平臺、編譯器版本等)中進行測試和編寫代碼,可能會因為環境的差異導致結果不同。
5. **代碼的實現細節**:在編寫代碼時,可能會因為實現的細節不同(例如,算法效率、邊界條件處理等)而在測試時得到不同的結果。
為了減少這些差異,通常需要在代碼中加入適當的注釋,說明可能存在的差異,并在文檔中詳細描述測試環境。此外,也可以使用標準庫函數,如`<stdint.h>`中定義的固定寬度整數類型,來避免因為浮點數精度和字節序引起的問題。在編寫和測試代碼時,應盡可能在相同的環境中進行,以確保結果的一致性。
assert 存在的意義
?assert可以確保傳過來的指針不是空指針
`assert`是一個在C語言中廣泛使用的宏,它的存在主要有以下意義:
1. **調試輔助**:在開發過程中,`assert`用于在運行時檢查程序的假設是否成立。如果`assert`語句的條件評估為假,則程序會拋出一個斷言失敗異常,通常會導致程序終止并輸出錯誤信息,這樣可以幫助開發者快速定位問題所在。
2. **靜態代碼分析**:在編譯時,`assert`可以作為靜態代碼分析的工具。通過檢查`assert`語句,編譯器可以確保程序在運行之前滿足一定的條件。這有助于在編譯階段發現潛在的錯誤,而不是在運行時才暴露出來。
3. **文檔和代碼的同步**:`assert`的使用可以在一定程度上作為代碼注釋,它表明了程序員對程序某一部分的預期。這樣,其他閱讀代碼的人可以更清楚地理解代碼的假設和約束。
4. **性能分析**:在某些情況下,`assert`可以用來測試代碼路徑是否應該被執行。如果`assert`失敗,這意味著程序處于一個不應該到達的狀態,這可能有助于發現性能瓶頸或邏輯錯誤。
5. **代碼簡潔性**:使用`assert`可以避免在代碼中充斥著大量的錯誤檢查和異常處理代碼,使得正常邏輯更加清晰。
6. **沙盒環境的創建**:在某些安全敏感的應用中,`assert`可以用來創建沙盒環境,確保程序在特定的安全邊界內運行。
總之,`assert`是C語言中一個非常有用的工具,它有助于提高代碼的質量和開發效率,同時也使得代碼更加健壯。然而,需要注意的是,`assert`不應該用于修復錯誤,而應該僅用于檢測錯誤。在生產環境中,應該確保`assert`語句在編譯時被正確處理,或者在運行時不會對程序的穩定性產生影響。
assert具體舉例?
需要包含頭文件assert.h?
這個代碼的健壯性更好
限制了所改變的內容
?———————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
strlen
strlen存在的意義
`strlen`是C語言標準庫函數之一,它的作用是計算字符串的長度,不包括終止符`\0`(空字符)。`strlen`函數在`string.h`或`stdlib.h`頭文件中定義。
`strlen`的意義和用途包括:
1. **獲取字符串長度**:`strlen`函數可以方便地獲取一個字符串的長度,這對于許多字符串操作(如字符串比較、復制、連接等)是非常必要的。
2. **內存分配**:在動態分配內存時,通常需要知道字符串的長度,以便為字符串加上結束符`\0`并分配足夠的空間。`strlen`可以提供這個長度信息。
3. **字符串處理**:在進行字符串處理時,如搜索、替換、截斷等操作,需要知道字符串的實際長度。`strlen`函數提供了這個信息。
4. **安全性**:在使用字符串時,`strlen`可以幫助避免緩沖區溢出等安全問題。例如,在復制字符串時,知道目標緩沖區的大小和源字符串的長度可以確保不會超出緩沖區的邊界。
5. **跨平臺兼容性**:`strlen`是C語言標準庫的一部分,因此它在不同的操作系統和編譯器之間具有很好的兼容性。
使用`strlen`時需要注意的是,由于`strlen`不考慮字符串中的寬字符或多字節字符,它只計算單字節字符的長度,因此在處理非ASCII字符串時可能不夠準確。對于寬字符或多字節字符串的長度計算,應使用相應的庫函數,如`wcslen`(寬字符串長度)或`strlen`的變體,這些函數能夠正確處理多字節字符。?
具體的舉例?
這里是和assert進行了一個結合?
利用assert代碼加入到代碼 里面會減少出錯性(這里涉及野指針的,下面會講到)?
包含頭文件
string.h或stdlib.h頭文件中定義
————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
指針的基本算法?
指針的作用就是訪問內存
指針的加法運算?
指針的運算和訪問內存息息相關下面會講到?
?
這里是整形,一個整形是四個字節,在指針的類型里面已經講到?
當然不能搞混
所以邏輯就是
代碼舉例
指針版本1
下標方式訪問
指針方式訪問
指針加減 整數
解引用
每次指針+1
每次加一之后則變化指向的數值
如圖?
指針版本2
這個運行的前提條件是數組在內存里面是連續存放的
指針的減法運算(指針-指針)
指針的運算
指針-指針
指針-指針絕對值
得到是兩個指針之間的元素個數
沒有意義
不是所有的計算都是有意義的
而指針的計算應該是有意義的
指針-指針的運算
低地址和高地址的區別
指針-指針限制條件必須指向同一空間
所以計算出的結果是空間的長度?
?
但是像這個計算出的結果 ,因為不是一個空間所以計算出的結果不是中間的長度,而是沒有意義的運算?
指針里面strlen的運算
統計的是\0之前出現的個數
包含頭文件
strlen頭文件string
數組名相當于首元素地址
字符的地址用字符指針接收
strlen 指針的運算方式1
這里是數組名每次進行++ 然后最后求出總的字符的長度?
strlen指針的運算方式2
這個采取的是指針-指針的運算方式
也就是同一空間 start=str 這里的str是首元素地址 也就是指向的是首元素
然后每次進行str++再減去首元素地址 也就是指針-指針的運算
因為
這里str最后指向的是str這個空間的最后一個元素
start指向的是身str的首元素地址 也就是這個空間的第一個元素
指針的關系大小的運算
區分?
運算的代碼?
這里是首元素的地址?
?
?sz是長度,p<arr+sz意思就是循環條件就是小于首元素地址加上整體長度
此時是不存在越界訪問的情況的因為是小于而不是是小于等于
然后打印*p
每次P++
從而實現代碼
————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
野指針以及越界訪問的問題
野指針的定義?
野指針(Wild Pointer)是指向未知或者不可訪問內存地址的指針。在C或C++編程語言中,野指針通常是由于指針沒有被正確初始化而產生的。這意味著指針不指向任何有效的內存地址,而是可能指向堆、棧或者代碼區的任意位置。
定義:
野指針是一種不安全指針,它的值沒有指向程序中已知地址的任何有效內存。這種指針的值可能是任意地址,也可能是一個隨機的舊值。在野指針上進行解引用操作(即通過指針訪問它所指向的內存)會導致未定義行為(Undefined Behavior),這通常會導致程序崩潰或者產生錯誤的結果。
野指針的常見來源包括:
1. 指針聲明后沒有立即初始化。
2. 指針釋放后沒有重新初始化。
3. 指針通過局部變量返回函數外,而該局部變量已經超出作用域。
4. 在動態內存分配失敗時,忽略錯誤并將指針置為`NULL`以外的值。
為了避免野指針,程序員應該遵循以下最佳實踐:
- 總是初始化指針,使其指向一個有效的內存地址,或者明確地將其設置為`NULL`。
- 在使用指針之前,檢查它是否為`NULL`。
- 避免指針的懸掛(dangling pointer)現象,即指針指向的內存已經被釋放。
- 使用現代C++特性,如智能指針,可以自動管理內存,減少野指針的風險。
指針正確代碼和錯誤代碼的對比
正確代碼?
錯誤代碼
也就是燙燙燙燙燙燙燙燙燙燙燙燙燙燙燙燙燙燙燙燙燙燙燙燙燙燙燙燙燙燙燙燙燙燙燙燙燙燙燙燙燙燙燙燙燙燙燙燙燙燙燙燙
這里為什么會產生燙這個字和函數棧有關,因為函數棧溢出的時候就會產生燙這個字
燙燙燙的問題
這里為什么會產生燙這個字和函數棧有關,因為函數棧溢出的時候就會產生燙這個字
在C語言編程中,棧溢出(stack overflow)是一個常見的問題,它發生在程序的棧空間被耗盡的時候。棧是用來存儲局部變量和函數調用的上下文信息(如返回地址、函數參數等)的區域。當一個函數調用自身或其他函數時,這些信息會被推入棧中。如果函數調用層次太深或者遞歸調用的深度超過了棧的大小限制,就會導致棧溢出。
棧溢出時,程序會出現未定義行為(undefined behavior),這可能導致程序崩潰、數據損壞或其他不可預測的結果。在中文里,并沒有“燙”這個字與棧溢出直接相關的說法。如果您在某個特定的上下文或方言中遇到了“燙”這個詞與編程錯誤或異常相關聯,那可能是一個特殊的用法或者是某個特定社群的俚語,而不是一個標準的計算機科學術語。
為了避免棧溢出,程序員應該:
1. 限制遞歸的深度,確保遞歸調用的層級不會超過棧的最大容量。
2. 使用尾遞歸優化(tail recursion optimization),如果編譯器或語言支持,這可以避免棧空間的增加。
3. 避免在棧上分配大量內存,特別是在遞歸函數中。
4. 使用堆(heap)來存儲較大的數據結構,而不是棧,因為堆的容量遠大于棧。
如果您在某個特定的文檔、教程或者社區中看到了“燙”這個詞與棧溢出相關的解釋,那么它可能是一個特定的術語或者是一個翻譯錯誤。在這種情況下,建議您查找更多的信息或者向該社區的成員澄清這個問題。
野指針造成的問題
野指針容易造成的問題
?野指針(Wild Pointer)是指向未知或者不可訪問內存地址的指針。在C或C++編程中,野指針通常是由于指針沒有被正確初始化而產生的。使用野指針進行解引用(dereferencing)會導致未定義行為(Undefined Behavior),這可能導致程序崩潰、數據損壞或其他不可預測的結果。
野指針造成的問題主要包括以下幾點:
1. **程序崩潰**:野指針可能指向內存中的任何位置,包括操作系統或其他程序的內存區域。解引用野指針可能會讀取或修改這些位置的內容,導致程序崩潰。
2. **數據損壞**:野指針可能導致對程序其他部分數據的意外修改,這種修改可能是不可逆的,從而導致程序行為變得不可預測。
3. **安全問題**:野指針可能被用于發起緩沖區溢出攻擊(Buffer Overflow Attack)。攻擊者可以利用野指針向程序中寫入惡意數據,從而覆蓋返回地址或其他關鍵數據,導致程序執行意外的代碼。
4. **性能下降**:野指針可能導致程序在運行時產生意外的分支,從而影響程序的性能。
5. **調試困難**:由于野指針導致的問題通常是未定義行為,因此在出現問題時,調試和定位錯誤可能會非常困難。
為了避免野指針造成的問題,程序員應該遵循以下最佳實踐:
- 總是初始化指針,使其指向一個有效的內存地址,或者明確地將其設置為`NULL`。
- 在使用指針之前,檢查它是否為`NULL`。
- 避免指針的懸掛(dangling pointer)現象,即指針指向的內存已經被釋放。
- 使用現代C++特性,如智能指針,可以自動管理內存,減少野指針的風險。
正確管理指針是確保軟件質量和穩定性的重要方面,也是避免野指針問題的關鍵。
野指針的非法訪問問題
形成非法訪問
可以看到每次的數值是隨機的
野指針的越界訪問問題
這里是10個元素循環11次
循環第十一次的時候就已經造成越界訪問
所以此時的指針就變成了野指針
指針指向的空間釋放
越界和傳址問題圖解?
?
野指針的尋址的問題
但是遺憾的是a的空間運行結束進行釋放 a的空間還給操作系統了 但是下面的空間還是進行尋址 也就是此時找的是空地址 此時這個指針就是野指針 此時野指針再訪問空間 就是非法訪問
這里不能指向這個空間 ,因為這里創建的空間最后會進行銷毀
也就是說如果指向這個空間的haul,最后指向的是一個空的地址
最后導致野指針的發生
簡單的舉例就是,小敏今天開了一個房間是901,準備進行詐騙。然后讓小帥第二天來,小帥第一天吧錢交出去之后,第二天來到這個酒店,說我要進去901,但是酒店說了,這個人退房了,已經沒有這個房間了。
這個就是指針的尋址問題,
因為在另外一個函數里面進行運算的事情,只能返回一個數值,而不是整個地址,這個函數需要進行運算的時候,或者需要進行詐騙的時候會開辟一塊空間,不需要進行詐騙會吧這個空間關閉。但是如果你還是需要回去那個空間,自然就找不到這個空間了。
越界和傳址問題圖解?
如何避免野指針
本來就是拴著的狗 你還去里面 肯定咬你
避免指針越界訪問
更加安全
assert斷言和野指針的使用
包含頭文件 assert.h
也就是assert進行斷言 如果p不等于空指針 則沒事
如果等于空指針 則 斷言失敗 報錯
直接報錯了 如果不滿足條件
這里是滿足條件才繼續往下走
斷言成功 繼續運行
斷言只要是表達式就可以 不一定非得說是指針
禁用很簡單
assert對程序員很友好
所以在寫代碼的時候適當assert一下 對你是刮目相看的
release是發布版本 發布出去 還斷言干什么
————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
傳值調用 傳址調用
兩者區別
首先就是顧名思義
傳值調用傳過去的是數值
傳址調用傳過去的是地址
在C語言中,函數對參數的傳遞有兩種方式:傳值調用(Pass by Value)和傳址調用(Pass by Reference)。
傳值調用:
在傳值調用中,函數接收的是實際參數值的一個副本。
這意味著在函數內部對參數的任何操作都不會影響原始的變量。
傳值調用適用于基本數據類型(如int、float等),因為這些數據類型可以輕松地通過值傳遞來復制。
傳址調用:
在傳址調用中,函數接收的是實際參數地址(即變量的內存地址)。
這意味著在函數內部對參數的任何操作都會直接影響原始的變量。
傳址調用通常用于指針類型或數組,因為這些數據類型無法簡單地通過值傳遞來復制,而是需要傳遞它們的地址。
簡單來說,傳值調用是傳遞數據的副本,而傳址調用是傳遞數據所在內存的地址。下面是一個示例,展示兩種調用方式的差異:
#include <stdio.h>// 傳值調用示例
void modifyValue(int value) {value = 50;
}// 傳址調用示例
void modifyReference(int *ptr) {*ptr = 50;
}int main() {int a = 25;// 使用傳值調用modifyValue(a);printf("After modifyValue: a = %d\n", a); // a 仍為 25// 使用傳址調用modifyReference(&a);printf("After modifyReference: a = %d\n", a); // a 變為 50return 0;
}
在上述代碼中,通過傳值調用修改的變量 a 的值沒有被改變,因為傳遞的是 a 的副本。而通過傳址調用修改的是變量 a 的值,因為傳遞的是 a 的地址。
兩者的情況分析
不是所有情況都適合
xy都有自己的空間
所以實際是xy進行交換半天 但是ab沒有進行交換
雖然形參得到了實參的數值 但是實參有自己的空間 對形參的修改不會影響實參
所以沒有達到最終的效果
怎么解決
所以此時就是間接找到了ab的地址
因為此時指針指向的是地址?
此時是直接找到地址
畫圖解釋‘
如果不指向地址的情況下 形參是開辟自己的空間 但是指向的是ab的地址從而對ab的地址進行操作
所以最終還是三個變量之間的交換
相當于就是直接對a b 操作
傳址調用
總結
也就是主要是函數內部可以操作外面 就需要傳址調用
如果只是接收數值 需要傳值調用
————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
補充
指針類型和C語言編程語言環境的區別
指針類型是C語言編程中的一個核心概念,而C語言編程語言環境則是指C語言本身及其相關的編譯器、運行時庫、操作系統等軟件和硬件環境的總和。下面分別解釋這兩者的區別:
### 指針類型
指針類型是指在C語言中用于存儲和管理內存地址的數據類型。指針本身是一個變量,其值是另一個變量的內存地址。通過指針,可以間接訪問和操作內存中的數據。
指針類型包括以下幾個方面:
1. **基本指針類型**:如`int *p;`中的`p`是一個指向整型的指針。
2. **空指針**:在C中,`NULL`是一個定義在`stddef.h`中的空指針常量,表示沒有指向任何有效內存地址的指針。
3. **指向指針的指針**:如`int **pp;`中的`pp`是一個指向整型指針的指針。
4. **函數指針**:指向函數的指針,可以用來調用函數,如`void (*fp)(int);`。
5. **數組指針**:指針可以指向數組,如`int (*arr)[10];`中的`arr`是一個指向含有10個整數的數組的指針。
指針類型在C語言中的使用非常廣泛,因為它們提供了內存操作的高效途徑,并且是實現數據結構(如鏈表、樹、圖等)的基礎。
### C語言編程語言環境
C語言編程語言環境包括了C語言本身以及使用C語言進行編程時所依賴的所有外部資源。這些資源可以包括:
1. **編譯器**:將C源代碼轉換為機器代碼的工具,如GCC、Clang等。
2. **運行時庫**:在程序運行時提供額外功能的庫,如標準庫`libc`,它包含了像`printf`、`malloc`這樣的函數。
3. **操作系統**:提供程序執行和管理硬件資源的平臺,如Linux、Windows等。
4. **開發工具**:包括編輯器、調試器、性能分析工具等,如Vim、Emacs、GDB等。
5. **硬件平臺**:指針類型和C語言的其他特性在不同的硬件平臺上可能有所不同,因為硬件架構會影響內存管理方式和指針的實現。
總結來說,指針類型是C語言中的一個語言特性,而C語言編程語言環境則是一個更為廣泛的概念,包括了C語言本身以及用于編寫和執行C程序的所有工具和平臺。
指針類型+1增加四個字節和編程環境+1增加四個字節的區別
在C語言中,"指針類型+1增加四個字節"和"編程環境+1增加四個字節"這兩者之間的區別在于它們所指的具體內容和上下文。
### 指針類型+1增加四個字節
1. **指針自增操作**:當你對一個指針進行`++`操作時,例如`int *p; p++;`,如果`int`類型占用四個字節,那么指針`p`的內容將增加四個字節,指向下一個`int`類型的位置。這是指針操作的一種,是指針本身值的改變。
2. **指針的移動**:這里的"增加四個字節"是指指針的數值增加了`sizeof(int)`的大小。`sizeof(int)`在大多數現代平臺上的確是四個字節,但這并不是固定的,它取決于編譯器和操作系統。
### 編程環境+1增加四個字節
1. **數據大小的變化**:在編程環境的上下文中,"+1增加四個字節"可能指的是在某個上下文中,數據類型的大小增加了。例如,如果編程環境從32位升級到64位,那么通常整數類型的大小會從四個字節增加到八個字節。
2. **性能優化或需求**:在某些情況下,編程環境的更新可能不會改變數據類型的大小,但可能會引入新的數據類型或者優化,以滿足更高的性能需求或者更好的兼容性。
3. **內存管理的變化**:在更廣泛的上下文中,"增加四個字節"也可能指的是內存管理策略的變化,比如操作系統或編譯器可能會在指針周圍增加額外的元數據,以支持更高級的內存分配或優化技術。
總的來說,"指針類型+1增加四個字節"是指針操作的直接結果,而"編程環境+1增加四個字節"是指整個編程語言和運行時環境的改變,這些改變可能影響數據類型的大小、內存布局、性能特性等多個方面。這兩者是不同層次的概念,一個是語言層面的操作,另一個是整個環境層面的變化。