當前位置:首頁 » 編程語言 » c語言內存模型

c語言內存模型

發布時間: 2023-04-05 12:47:18

A. c語言和C++的內存模型一樣嗎

堆棧是一樣的。這些細節編程的人敏歲不用管。new出來的空間是自動清襪管理的,編橋正睜程者不用操心。malloc是編程人自己直接管理空間的,你索要了空間,最後要歸還。因為系統不知道你什麼時候不需要你申請的空間了。

B. C語言怎麼寫底層.這是什麼情況

C語言的內存模型基本上對應了現在von Neumann(馮.諾伊曼)計算機的實際存儲模型,很好的達到了對機器的映射,這是C/C++適合做底層開發的主要原因,另外,C語言適合做底層開發還有另外一個原因,那就是C語言對底層操作做了很多的的支持,提供了很多比較底層的功能。

下面結合問題分別進行闡述。

問題:移位操作

在運用移位操作符時,有兩個問題必須要清楚:

(1)、在右移操作中,騰空位是填 0 還是符號位;

(2)、什麼數可以作移位的位數。

答案與分析:

">>"和"<<"是指將變數中的每一位向右或向左移動, 其通常形式為:

右移: 變數名>>移位的位數

左移: 變數名<<移位的位數

經過移位後, 一端的位被"擠掉",而另一端空出的位以0 填補,在C語言中的移位不是循環移動的。

(1) 第一個問題的答案很簡單,但要根據不同的情況而定。如果被移位的是無符號數,則填 0 。如果是有符號數,那麼可能填 0 或符號位。如果你想解決右移操作中騰空位的填充問題,就把變數聲明為無符號型,這樣騰空位會被置 0。

(2) 第二個問題的答案也很簡單:如果移動 n 位,那麼移位的位數要不小於 0 ,並且一定要小於 n 。這樣就不會在一次操作中把所有數據都移走。

比如,如果整型數據占 32 位,n 是一整型數據,則 n << 31 和 n << 0 都合法,而 n << 32 和 n << -1 都不合法。

注意即使騰空位填符號位,有符號整數的右移也不相當與除以 。為了證明這一點,我們可以想一下 -1 >> 1 不可能為 0 。
問題:位段結構

struct RPR_ATD_TLV_HEADER
{
ULONG res1:6;
ULONG type:10;
ULONG res1:6;
ULONG length:10;
};

位段結構是一種特殊的結構, 在需按位訪問一個位元組或字的多個位時, 位結構比按位運算符更加方便。

位結構定義的一般形式為:

struct位結構名{

數據類型 變數名: 整型常數;

數據類型 變數名: 整型常數;

} 位結構變數;

其中: 整型常數必須是非負的整數, 范圍是0~15, 表示二進制位的個數, 即表示有多少位。

變數名是選擇項, 可以不命名, 這樣規定是為了排列需要。

例如: 下面定義了一個位結構。

struct{
unsigned incon: 8; /*incon佔用低位元組的0~7共8位*/
unsigned txcolor: 4;/*txcolor佔用高位元組的0~3位共4位*/
unsigned bgcolor: 3;/*bgcolor佔用高位元組的4~6位共3位*/
unsigned blink: 1; /*blink佔用高位元組的第7位*/
}ch;

位結構爛穗猛成員的訪問與結構成員的訪問相同。

例如: 訪問上例飢橋位結構中的bgcolor成員可寫成:

ch.bgcolor

位結構成員可以與其它結構成員一起使用。 按位訪問與設置,方便&節省

例如:

struct info{
char name[8];
int age;
struct addr address;
float pay;
unsigned state: 1;
unsigned pay: 1;
}workers;'

上例的結構定義了關於一個工從的信息。其中有兩個位結構成員, 每個位結構成員只有一位, 因此只佔一個位元組但保存了兩個信息, 該位元組中第一位表示工人的狀態, 第二位表示工資是否已發放。由此可見使用位結構可以節省存貯空間。

注意不要超過值限制

問題:位元組對齊

我在使用VC編程的過程中,有一次調用DLL中定義的結構時,發覺結構都亂掉了,完全不能讀取正確的值,後來發現這是因為DLL和調用程序使用的位元組對齊選項不同,那麼我想問一下,位元組對齊究竟是怎麼一回事?

答案與分析:

關於位元組對齊:

1、 當不同的族歲結構使用不同的位元組對齊定義時,可能導致它們之間交互變得很困難。

2、 在跨CPU進行通信時,可以使用位元組對齊來保證唯一性,諸如通訊協議、寫驅動程序時候寄存器的結構等。

三種對齊方式:

1、 自然對齊方式(Natural Alignment):與該數據類型的大小相等。

2、 指定對齊方式 :

#pragma pack(8) //指定Align為 8;

#pragma pack() //恢復到原先值

3、 實際對齊方式:

Actual Align = min ( Order Align, Natual Align )

對於復雜數據類型(比如結構等):實際對齊方式是其成員最大的實際對齊方式:

Actual Align = max( Actual align1,2,3,…)

編譯器的填充規律:

1、 成員為成員Actual Align的整數倍,在前面加Padding。

成員Actual Align = min( 結構Actual Align,設定對齊方式)

2、 結構為結構Actual Align的整數倍,在後面加Padding.

例子分析:

#pragma pack(8) //指定Align為 8

struct STest1
{
char ch1;
long lo1;
char ch2;
} test1;
#pragma pack()

現在

Align of STest1 = 4 , sizeof STest1 = 12 ( 4 * 3 )
test1在內存中的排列如下( FF 為 padding ):
00 -- -- -- 04 -- -- -- 08 -- -- -- 12 -- -- --
01 FF FF FF 01 01 01 01 01 FF FF FF
ch1 -- lo1 -- ch2
#pragma pack(2) //指定Align為 2
struct STest2
{
char ch3;
STest1 test;
} test2;
#pragma pack()
現在 Align of STest1 = 2, Align of STest2 = 2 , sizeof STest2 = 14 ( 7 * 2 )

test2在內存中的排列如下:

00 -- -- -- 04 -- -- -- 08 -- -- -- 12 -- -- --
02 FF 01 FF FF FF 01 01 01 01 01 FF FF FF
ch3 ch1 -- lo1 -- ch2

注意事項:

1、 這樣一來,編譯器無法為特定平台做優化,如果效率非常重要,就盡量不要使用#pragma pack,如果必須使用,也最好僅在需要的地方進行設置。

2、 需要加pack的地方一定要在定義結構的頭文件中加,不要依賴命令行選項,因為如果很多人使用該頭文件,並不是每個人都知道應該pack。這特別表現在為別人開發庫文件時,如果一個庫函數使用了struct作為其參數,當調用者與庫文件開發者使用不同的pack時,就會造成錯誤,而且該類錯誤很不好查。

3、 在VC及BC提供的頭文件中,除了能正好對齊在四位元組上的結構外,都加了pack,否則我們編的Windows程序哪一個也不會正常運行。

4、 在 #pragma pack(n) 後一定不要include其他頭文件,若包含的頭文件中改變了align值,將產生非預期結果。

5、 不要多人同時定義一個數據結構。這樣可以保證一致的pack值。 問題:按位運算符

C語言和其它高級語言不同的是它完全支持按位運算符。這與匯編語言的位操作有些相似。 C中按位運算符列出如下:

━━━━━━━━━━━━━━━━━━━━━━━━━━━━

操作符 作用

————————————————————————————

& 位邏輯與

位邏輯或

^ 位邏輯異或

- 位邏輯反

>> 右移

<< 左移

━━━━━━━━━━━━━━━━━━━━━━━━━━━━

注意:

1、 按位運算是對位元組或字中的實際位進行檢測、設置或移位, 它只適用於字元型和整數型變數以及它們的變體, 對其它數據類型不適用。

2、 關系運算和邏輯運算表達式的結果只能是1或0。 而按位運算的結果可以取0或1以外的值。 要注意區別按位運算符和邏輯運算符的不同, 例如, 若x=7, 則x&&8 的值為真(兩個非零值相與仍為非零), 而x&8的值為0。

3、 與 ,&與&&,~與! 的關系

&、 和 ~ 操作符把它們的操作數當作一個為序列,按位單獨進行操作。比如:10 & 12 = 8,這是因為"&"操作符把 10 和 12 當作二進制描述 1010 和 1100 ,所以只有當兩個操作數的相同位同時為 1 時,產生的結果中相應位才為 1 。同理,10 12 = 14 ( 1110 ),通過補碼運算,~10 = -11 ( 11...110101 )。<以多少為一個位序列> &&、 和!操作符把它們的操作數當作"真"或"假",並且用 0 代表"假",任何非 0 值被認為是"真"。它們返回 1 代表"真",0 代表"假",對於"&&"和""操作符,如果左側的操作數的值就可以決定表達式的值,它們根本就不去計算右側的操作數。所以,!10 是 0 ,因為 10 非 0 ;10 && 12 是 1 ,因為 10 和 12 均非 0 ;10 12也是 1 ,因為 10 非 0 。並且,在最後一個表達式中,12 根本就沒被計算,在表達式 10 f( ) 中也是如此。

C. c語言中的void printlog(char *format,...)這是什麼意思

是可變參數,是c的一個語法現象,我在電腦上保存的一些資料,希望對你有用。
一、什麼是可變參數
我們在C語言編程中有時會遇到一些參數個數可變的函數,例如printf()函數,其函數原型為:
int printf( const char* format, ...);
它除了有一個參數format固定以外,後面跟的參數的個數和類型是可變的(用三個點"…"做參數佔位符),實際調用時可以有以下的形式:
printf("%d",i);
printf("%s",s);
printf("the number is %d ,string is:%s", i, s);
以上這些東西已為大家所熟悉。但是究竟如何寫可變參數的C函數以及這些可變參數的函數編譯器是如何實現,這個問題卻一直困擾了我好久。本文就這個問題進行一些探討,希望能對大家有些幫助.

二、可變參數在編譯器中的處理
我們知道va_start,va_arg,va_end是在stdarg.h中被定義成宏的, 由於1)硬體平台的不同 2)編譯器的不同,所以定義的宏也有所不同,下面看一下VC++6.0中stdarg.h里的代碼(文件的路徑為VC安裝目錄下的\vc98\include\stdarg.h)
typedef char * va_list;
#define _INTSIZEOF(n) ((sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) )
#define va_start(ap,v) ( ap = (va_list)&v + _INTSIZEOF(v) )
#define va_arg(ap,t) ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) )
#define va_end(ap) ( ap = (va_list)0 )

下面我們解釋這些代碼的含義:

1、首先把va_list被定義成char*,這是因為在我們目前所用的PC機上,字元指針類型可以用來存儲內存單元地址。而在有的機器上va_list是被定義成void*的

2、定義_INTSIZEOF(n)主要是為了某些需要內存的對齊的系統.這個宏的目的是為了得到最後一個固定參數的實際內存大小。在我的機器上直接用sizeof運算符來代替,對程序的運行結構也沒有影響。(後文將看到我自己的實現)。

3、va_start的定義為 &v+_INTSIZEOF(v) ,這里&v是最後一個固定參數的起始地址,再加上其實際佔用大小後,就得到了第一個可變參數的起始內存地址。所以我們運行va_start(ap, v)以後,ap指向第一個可變參數在的內存地址,有了這個地址,以後的事情就簡單了。

這里要知道兩個事情:
⑴在intel+windows的機器上,函數棧的方向是向下的,棧頂指針的內存地址低於棧底指針,所以先進棧的數據是存放在內存的高地址處。
(2)在VC等絕大多數C編譯器中,默認情況下,參數進棧的順序是由右向左的,因此,參數進棧以後的內存模型如下圖所示:最後一個固定參數的地址位於第一個可變參數之下,並且是連續存儲的。
|--------------------------|
| 最後一個可變參數 | ->高內存地址處
|--------------------------|
|--------------------------|
| 第N個可變參數 | ->va_arg(arg_ptr,int)後arg_ptr所指的地方,
| | 即第N個可變參數的地址。
|--------------- |
|--------------------------|
| 第一個可變參數 | ->va_start(arg_ptr,start)後arg_ptr所指的地方
| | 即第一個可變參數的地址
|--------------- |
|------------------------ --|
| |
| 最後一個固定參數 | -> start的起始地址
|-------------- -| .................
|-------------------------- |
| |
|--------------- | -> 低內存地址處

(4) va_arg():有了va_start的良好基礎,我們取得了第一個可變參數的地址,在va_arg()里的任務就是根據指定的參數類型取得本參數的值,並且把指針調到下一個參數的起始地址。
因此,現在再來看va_arg()的實現就應該心中有數了:
#define va_arg(ap,t) ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) )
這個宏做了兩個事情,
①用用戶輸入的類型名對參數地址進行強制類型轉換,得到用戶所需要的值
②計算出本參數的實際大小,將指針調到本參數的結尾,也就是下一個參數的首地址,以便後續處理。

(5)va_end宏的解釋:x86平台定義為ap=(char*)0;使ap不再 指向堆棧,而是跟NULL一樣.有些直接定義為((void*)0),這樣編譯器不會為va_end產生代碼,例如gcc在linux的x86平台就是這樣定義的. 在這里大家要注意一個問題:由於參數的地址用於va_start宏,所以參數不能聲明為寄存器變數或作為函數或數組類型. 關於va_start, va_arg, va_end的描述就是這些了,我們要注意的 是不同的操作系統和硬體平台的定義有些不同,但原理卻是相似的.

三、可變參數在編程中要注意的問題
因為va_start, va_arg, va_end等定義成宏,所以它顯得很愚蠢, 可變參數的類型和個數完全在該函數中由程序代碼控制,它並不能智能 地識別不同參數的個數和類型. 有人會問:那麼printf中不是實現了智能識別參數嗎?那是因為函數 printf是從固定參數format字元串來分析出參數的類型,再調用va_arg 的來獲取可變參數的.也就是說,你想實現智能識別可變參數的話是要通過在自己的程序里作判斷來實現的. 例如,在C的經典教材《the c programming language》的7.3節中就給出了一個printf的可能實現方式,由於篇幅原因這里不再敘述。

四、小結:
1、標准C庫的中的三個宏的作用只是用來確定可變參數列表中每個參數的內存地址,編譯器是不知道參數的實際數目的。
2、在實際應用的代碼中,程序員必須自己考慮確定參數數目的辦法,如
⑴在固定參數中設標志-- printf函數就是用這個辦法。後面也有例子。
⑵在預先設定一個特殊的結束標記,就是說多輸入一個可變參數,調用時要將最後一個可變參數的值設置成這個特殊的值,在函數體中根據這個值判斷是否達到參數的結尾。本文前面的代碼就是採用這個辦法.
無論採用哪種辦法,程序員都應該在文檔中告訴調用者自己的約定。
3、實現可變參數的要點就是想辦法取得每個參數的地址,取得地址的辦法由以下幾個因素決定:
①函數棧的生長方向
②參數的入棧順序
③CPU的對齊方式
④內存地址的表達方式
結合源代碼,我們可以看出va_list的實現是由④決定的,_INTSIZEOF(n)的引入則是由③決定的,他和①②又一起決定了va_start的實現,最後va_end的存在則是良好編程風格的體現,將不再使用的指針設為NULL,這樣可以防止以後的誤操作。
4、取得地址後,再結合參數的類型,程序員就可以正確的處理參數了。理解了以上要點,相信稍有經驗的讀者就可以寫出適合於自己機器的實現來。

D. 906c語言程序設計難嗎

不難。906c語言程序設計入門不難,它的規則和限制不多,但是想學的深學的精很難,c語言和硬體緊密相關,處於相對比較底層的設計,如果對硬體完全不了解,就很難理解指針的概念,指針又是c語言的靈魂,很多時候和內存模型,外設高度相關,建議在學習c語言的時候,了解點硬體知識。

E. c語言中計算結果怎麼存儲

所有C函數,只要其有返回值,都是由寄存器Ax(對於32位而言為梁遲缺EAX,如果AX不足以容納,則由DX保存超過AX容量的部分)保存的(也可能是值,也可能是某一地址,視返回類型而定)。
上面旦睜這個C函數程序,計算(x>y?x:y);後,該值就存在橡辯AX(EAX)中,當你在其他函數中調用該函數
後,比如
a=max(m,n);後,其實就是進入max()完成後,函數返時將計算結果存放在AX中,執行這個語句後,即就將AX的值傳給了a.
其實,這也很容易驗證:
在調用
a=max(m,n);
緊接著輸入以下語句(注意:這中間絕對不能有其他任何函數調用和語句):
b=_AX;
printf("a=%d,b=%d",a,b);
你就可能發現,a=和b=一定是同一個值。

F. C語言代碼組成 - BSS、Data、Stack、Heap、Code、Const

一段C語言經過編譯連接後,成為一段可以運行的代碼,可運行的代碼可以分為以下四個部分組成:全局變數/靜態變數區、堆、棧、代碼區。其中全局變數/靜態變數區又分為未初始化變數區和初始化變數區,代碼區又分為代碼和常量區。即匯總下來,代碼可以分為6部分組成,包括:BSS區(未初始化的全局變數/靜態變數區)、Data區(實始化的全局變數區)、Stack區(棧區)、heap區(堆區)、Code區(代碼區)、const區(常量區)。

一、BSS區和Data區

C語言編程中定義的全局變數、靜態局部變數,就是分配在全局變數/靜態變數區域,但是為什麼又要分為BSS區域和Data區域呢?其實我們在定義全局或者靜態變數區,有時我會對它賦初始值,有的又不會賦初始化,比如我們定義的全局變數,初始化的賦值,是怎麼樣寫到變數區域中的,我們定義的靜態局部變數,在定義時初始化後,為什麼後面函數被調用,又不會再初始化呢?這個局部靜態變數是怎麼樣實始化的,什麼時候初始化的?

如果分析編譯後的匯編代碼,就會發現在代碼運行起來後,會有一段給變數賦值的指令,這一段代碼,不是我們C代碼對應的匯編,而是C編譯器生成的匯編譯代碼,這段代碼的作用就是給初始化了的靜態變數和全局變數進行初始化。這也是為什麼全局/靜態變數區域,要分BSS和Data的原因。

二、Stack區

棧是一種先進後出的數據結構,這種數據結構正好完美的匹配函數調用時的模型過程,比如函數f(a)在運行過程中調用函數f(b),f(a)在運行過程中的變數就是分配在棧中,通過在調用f(b)前,會將代碼中用到的R0~Rn寄存器的值保存到棧中,同時將函數的傳入參數寫入到棧中,然後進入f(b)函數,函數f(b)的變數b分配在棧中,當函數運行完畢後,釋放變數b,將棧中存放的f(a)函數的運行的R0~Rn寄存器值恢復到寄存器中,同時f(b)的返回結果存入到棧中,這樣f(a)繼續運行。當一個函數運行完畢後,它在棧中分配的臨時變數會全部釋放。

對於中斷也是一樣的,中斷發生時,也是一個函數打斷了另一個函數的運行,這種現場的保存(即寄存器的值),都是通過棧來完成的。所以棧的作用有:

三、Heap區

全局變數分配的內存在代碼整個運行周期內都是有效的,而在棧區分配的內存在函數調用完成後,就會釋放。這兩種內存模型都是由編譯器決定它的使用,代碼是無法控制的。那有沒有內存是由用戶控制的,要用時,就自由分配,不用時,就自行釋放?答案是肯定的,這部分內存就是堆。

用戶需要使用的動態內存,就是通過malloc函數,調用分配的,在沒有釋放前,可一直由代碼使用。當這部分內存不再需要使用時,可以通過free函數進行釋放,將它歸還到堆中。從這中可以看出,堆的內存,是按需分配的。這就是賦予了代碼很大的自由度,但這也是會帶來負作用的,比如:內存碎片化導致的malloc失敗;忘記釋放內存導致的內存泄露,而這些往往是致命的失誤。

四、Code區

代碼區就是編譯後機器指令,這些指令決定了功能的執行。我們編譯的代碼一般是下載進flash中,但是運行,卻有兩種方式:在RAM中運行和在ROM中運行。 在RAM中運行,即是boot啟動後,將flash中的代碼復制到RAM中,然後PC指針在指到RAM中的代碼中開始運行。 有時在調試時,我們可以直接將代碼下載進RAM中運行進行調試,這樣加快調試速度。便是大部分的情況我們的代碼是從flash中開始運行的。

五、常量區

代碼中的常量,一部分是作為立即數,在代碼區中,但是像定義的字元串、給某數組賦值的一串數值,這些常量,就存在常量區,我們常用const來定義一個常量,即該變數不能再必變。這部分的變數,編譯器一般將它定義的flash中。

六、各個區域大小的是如何決定的:

code區和const區:是由代碼的大小和代碼中常量的多少來決定的。

bss區和data區:這是由代碼中定義的全局變數和局部變數的多少來決定的。

stack區:這個可以由使用都自行定義大小,但使用都要根據自已代碼的情況,評估出一個合理的值,再定義其大小,如果定義的太小,很容易爆棧,導至代碼異常,但是如果定義的太大,就容易浪費內存。

heap區:RAM剩下的部分,編譯器就會作為堆區使用。

七、嵌入式代碼一般啟動過程

以STM32為例,通過分析其匯編啟支代碼,大致可以分為以下幾個步驟:

如果大家想看編譯扣,代碼文件的組成,可以查看統後生的map文件,裡面有詳細的數據,包括各個函數的分配內存,BSS,Data,Stack,Heap,Text的分配情況。

如果相要了解詳細的代碼啟動過程,可看它的啟動匯編文件。

G. c語言指針在什麼情況下需要malloc,什麼時候可以不這樣,(被這個問題搞暈了)

  1. C語言的內存模型包含有棧和堆兩部分。

  2. 棧存放的是函數執行時答圓的變燃尺量等,這部分變數由系統自動管理,比如 int a;等,這些變數在函數體結束時自動收回。

  3. 堆存放的是由用戶自己手動管理的變數,這些變數是的由malloc函數建立,由 free函數釋放。不皮舉高會在函數體結束時自動收回。

H. C語言重新賦值內存地址會變嗎

變數a的地址是不能變的,當程序被載入時,操作系統會為它分配好地址,且一經分配,不能再改變!當然,每一次程序運行時,操作系統為a分配的地址可以不同。

有以下幾點常常另初學者感到困惑,現舉例加以解釋:
int b,c,*a;
a=&b;
語句a=&b;並沒有改變a的地址,它只是改變了a這個箱子中裝的東西。如果你在語句a=&b;的前後用printf("%d",(int)&a);輸出a的地址,就會發現它們是一樣的。如果後面再來個a=&c;則printf("%d",(int)&a);的輸出也一樣!

關鍵是要區分以下幾點:
a,表示a的值,即它裝的東西,具體到這個例子,a裝的是另一個int型變數的地址。如果a不是const類型的,則它裝的東西可以改變。比如,這里先裝的是b的地址(指針變數是用來裝地址的),後改成了c的。
&a,當然就表示a自己的地址了,你可以將a想像成一個箱子,它的地址就是這個箱子的編號。
*a,因為a是一個指針,*a就表示a指向的變數的值,即b或c的值(具體要看a裝的是誰的地址,即a指向誰),也即*a=b或*a=c。

現假設a裝的是b的地址,那有:
a=&b,即a的值等於b的地址。
*a=b=*(&b),這里*的作用是取出某個地址中的值。因為a的值是b的地址,因此*a取出的是b的值,同理(&b)是b的地址,*(&b)取出的也是b的值!

分析一下a,b,c的內存模型(即它們在內存中是怎樣表示的、關系又是怎樣的),理解這些就不難了,你邊學邊體會吧!

I. C中指針變數何時需要初始化malloc

首先你要明白什麼是指針,指針是用來操作內存的。那麼指針又如何操作內存呢?在C語言里可以定義指針變數,這個指針變數里可以存儲內存的地址,一個32位的無符號整型值。它就像普通的int, double型變數一樣。以下面為例說明:
int iMax = 1;
int * pMax = NULL;
我們定義了一個int型的變數iMax 和一個int型的指針變數pMax,並對他們進行了初始化。這里iMax的值為1; pMax的值為NULL,也就是一個無符號整形0。注意NULL是一個宏,代表0。現在pMax的值為NULL,一般來講0也是一塊內存地址, 我們也可以操作。注意這個NULL你現在認為他表示無效即可。用來給指針進行初始化。其它的先不用管。
現在我們來使用pMax, 如果要使用pMax, 那麼就要對pMax賦值,使它指向一塊內存。我們這里定義指針的類型為一個指向一個int型值的指針變數。所以可以將iMax的地址賦值給pMax。注意,不管什麼樣的類型的指針變數,其值都是一個unsigned int型的值,表示內存的地址。理解這一點很重要。為什麼需要定義指針所指向的類型呢。如 char *, int * , double *型的指針,原因是我們使用指衫褲針是為了操作存儲在內存中的特定類型的值。如果沒有定義指針的類型,那我們在操作內存時,只能一個位元組, 一個位元組使用。這樣的指針沒昌塌芹有什麼意義,也許你還不太理解。但多應用就能明白這一點。
現在我們來給pMax賦值,然後操作它.
pMax = &iMax;
好了,可以使用pMax了。就像耐畢使用iMax一樣用它。不過你得在它前面加個指針運算符'*';
*pMax = *pMax + 2;
現在的pMax指針變數中存儲的是iMax變數地址的值。對*pMax操作, 就是對iMax操作。現在*pMax = 3 , iMax = 3;
如果說我臨時需要一塊內存,這塊內存用來存儲n個int的變數。我就需要使用malloc為pMax分配一塊內存。可以這樣做:
pMax = malloc(sizeof(int) * n);
if (pMax == NULL) // 錯誤處理
{ TODO...}
這樣我們就為pMax分配了一塊內存大小為sizeof(int) *n 位元組的內存。這里malloc返回一個指向這塊內存的首地址並將它賦給了int型指針變數pMax.
好了,pMax已經可以使用了。我們需要對它進行初始化。這個可以使用memset函數
memset(pMax, 0 , sizeof(int) * n);
現在就可以像數組一樣操作這塊int型的內存了。
pMax[0] = iMax;
pMax[1] = iMax + 1;
pMax[2] = pMax[0];
...
總的來說,指針非常靈活。因為它可以直接操作內存。這就會使指針這個東西很不容易控制。
像你說的p = temp ;將數組的首地址賦值給p , 這樣只是為了更容易操作字元串。temp也表示字元串的首地址, 但他是一個不可改變的量,即不能對temp賦值,它是只讀的。指針p就不同了,他可以進行一些數學運算。
對一個程序來講,如果你臨時需要一塊內存來存儲數據,你可以使用malloc, 但記得要free。否則容易造成內存泄露。
就這些吧。 希望對你有用。寫這么多也不容易,給點分吧。^_^

J. 關於C語言中數組作為參數傳遞的疑惑~~

湊湊熱鬧,同意terry_tang的觀點,另做些補充:

先看代碼:

#include <stdio.h>段謹鏈

void foo(int array[2]){printf("int array[2]:\t\t%x %d\n", &array, sizeof(array));}
void bar(int array[]){printf("int array[]:\t\t%x %d\n", &array, sizeof(array));}
void baz(int (&array)[2]){printf("int (&array)[2]:\t%x %d\n", &array, sizeof(array));}

int main()
{
int a[2] = {1, 2};
printf("main::a[2]:\t\t%x %d\n", a, sizeof(a));
foo(a);
bar(a);
baz(a);

return 0;
}

解說:

foo和bar的傳值方式是相同的,都是一個int*, 即一個整型指針,這可以從foo和bar里列印出的array地址和main中的不同和sizeof(array)僅為sizeof(int*)看出,只不過是外型有點兒區別。

編譯器是不知道你要傳遞的是一個數組或是單一一個整型的地址的,這是因為C中數組的內存模型是連續存儲(它並不知道傳遞的(首)地址之後的空間可否訪問)。

所以寫為foo或bar的樣式僅僅是對人的一種暗示,暗示傳遞的是一個數組晌哪,括弧里的2編譯器是不會把他當回事兒的①。

採用foo中的樣式,代碼編寫者在函數中獲知傳遞的數組的大小,但這種暗示功能很弱,而且易使人產生誤解。

比如以上的函數foo,傳遞大小為1個元素的數組(即單一一個整型的地址):

int x[1];
foo(x);

或傳遞一個大小為100的數組:

int x[100];
foo(x);

編譯器都不會有任何抱怨,所以在代碼工程量很大的時候,你無法保證數組傳值的安全性,另外一個問題是如果你寫的是商業性質的庫,你無法保證客戶(二次開發者)能安全地使用你的代碼。

採用bar中樣式,實質和foo相同,空括弧給人的暗示就是它能接受的參數是一個數組,而且是一個長度不確定的一維整型數組,這相對於foo來說更為實際和真實一些(因為foo可能造成欺騙性的代碼,原因見上)。

所以這種傳數組的方式被多數人所採用,但一般還需多加一個參數來指定數組的大小,如:

void bar(int array[], int size);

或效仿STL的做法,傳遞數組的首地址和超尾指針(在遍歷數組元素時很方便,且更快速、安全):

void bar(int* beg, int* end);

至於baz,它不同於foo和bar。前面已經說過,foo和bar實質是相同的,傳的都是一個int*,且傳值方式都是按值傳遞(C中只有按值傳遞)。

而baz卻是按引用傳遞,傳遞的是一個"編譯器認可的,大小為2"②的數組的引用。

foo和bar都可以改寫為:

void theFact(int* array);

void theFact(int* array, int size);

按照此邏輯是不是baz可改寫為這樣呢?

void baz2(int* const& array); // a其實是一個int* const型指針,所以要加上const作為修飾

答案是否定的,注意上面的②,只有在C++中,函數"按引用"傳遞數組並"指定其大小時",[]中的數字才有意義(對編譯器而言)。所以baz2 != baz:

int x[100];

baz(x); // 編譯錯誤
baz2(x); // 可以通過

要理解這和foo, bar的不同首先要理解C++中對引用的定義: 引用就是對象本身,不存在沒有引用對象的引用。所以在baz中,形參array就是實參main中的a,一切a所有的特性都是array的特性,所以sizeof(array) == sizeof(a),而且&baz::array == main::a(地址相同)。

①: C99中允許使握孫用static數組參數修飾詞,如:

void foo(int x[static 10]); // x數組至少含有10個連續元素

上句中的10此時並不是可有可無的,它是編譯器優化數組訪問的一種暗示。

熱點內容
早期存儲卡 發布:2024-11-02 14:26:50 瀏覽:988
配音秀緩存在手機哪裡 發布:2024-11-02 14:23:27 瀏覽:294
linux下載gcc 發布:2024-11-02 14:13:47 瀏覽:344
寫演算法交易 發布:2024-11-02 13:57:09 瀏覽:208
安卓怎麼下載鴻蒙 發布:2024-11-02 13:36:13 瀏覽:663
加密狗rsa 發布:2024-11-02 13:20:44 瀏覽:560
實用java教程 發布:2024-11-02 13:07:39 瀏覽:930
ide文件夾 發布:2024-11-02 12:51:37 瀏覽:559
python中字典的用法 發布:2024-11-02 12:40:42 瀏覽:28
安卓怎麼下載zine 發布:2024-11-02 12:40:38 瀏覽:793