C語言存儲類型總結(malloc,typedef)

一、存儲模型
1.存儲類型(storage class)
變量可以通過
生存週期(內存佔用時間->靜態與動態)
作用域(有效區域->全局與靜態)、連接點來描述。
1)        auto
普通局部變量,是自動存儲,變量會自動分配和釋放,函數內的變量複合語句內聲明的變量,在系統運行時分配空間賦初值,在調用結束時釋放空間,這類變量就是auto變量,
auto變量由系統自動分配在 棧空間。auto可以省略。
auto變量在代碼塊內。
棧空間值不初始化數值不確定。
全局變量不能聲明爲auto類型,因爲兩者在內存分配上機制不同。
1)        static
 
靜態數據類型,定義的數據存放在全局數據區,編譯的時賦初值,程序結束才釋放空間。沒有初始化的static變量值爲0
函數形參不能聲明爲靜態類型,函數形參通過堆棧完成,用於支持遞歸調用。
函數外定義的變量會具有內部鏈接屬性,作用域從定義開始到文件結束 非本文件不能引用。     
函數內定義的static變量是空鏈接屬性,作用域在本函數。
靜態函數是在函數前加static,彙編時只是把.globle符號去掉,使函數只在本文件生效,參數依然進出棧。
static
其實這個關鍵字有三個作用,而不僅僅是存儲類型。請看下面代碼:
// 1.修飾函數,使其只能在本文件可見
static void func(void)
{
    static int n = 0; // 2. 修飾局部變量,使其存儲在靜態區(存儲類型)
    printf("%d\n", n);
}
static int global; // 3. 修飾全局變量,使其只能在本文件可見
注意到,staticC語言中的三個作用,其中第1和第3個作用其實都是一樣的,改變的是函數或者變量的可見範圍。
只有當用static來修飾局部變量的時候,它的作用纔是代表一個存儲區域。
 
2)        register
請求編譯器將變量保存在寄存器中,從而加快程序的運行,對於頻繁使用的變量,存放在寄存器中可以減少和內存讀取(程序中遇到變量時用控制器發指令將變量的值送到運算器中,需要存數再保存到內存中)
register只能對局部變量和函數形參聲明(全局變量不可申請register類型)???
register不能取址, register int j; int *p=&j;是不允許的,因爲無法對寄存器定製。
register申請存放在寄存器時,不一定成功,因爲寄存器數量有限, 在申請失敗時,會默認成auto類型。
cpu的寄存器對數據類型有限制,不是所有類型變量都可以申請爲寄存器類型。如:有些系統只支持將intchar和指針變量定義爲register
3)        extern
extern把變量說明爲外部變量,表示這些變量已在其它文件中定義,編譯系統不再爲它們分配內存空間。
 
1.作用域(scope)
作用域是變量在程序中被使用的區域。編譯確認不同的類型的作用域有4種:
文件作用域、函數、代碼塊(block scope)、原型作用域(prototype scope)
代碼塊作用域: 是在大括號{ }內定義的部分;
函數作用域: 只適用於goto語句的語句標籤,且函數中的所有語句標籤必須唯一。
文件作用域: 任何在函數外聲明的變量都具有文件作用域(全局變量)
原型作用域 函數原型中聲明的參數名,編譯器只檢查參數類型,不關心參數名。
1)        局部變量()
函數內部定義的變量,只在本函數有效。複合語句中定義的變量在複合語句內有效;
void main()
{
int m, n;
}
char fun(int x, int y)
{ int i, j;
}
float fun(int a)
{ int b, c;
}
void main(){
int a, b;
{ int c;
c = a+b;
}
m, n 只在主函數中有效。不在整個程序中有效,函數平行。 形參也是局部變量,在整個行數中有效。 不同函數可以定義相同名字的變量,(佔用的內存不同); a,b在整個main函數中有效,c在複合大括號中有效,離開復合語句就會無效。
 
4)        全局變量(文件)
在函數之外定義的變量爲全局變量(外部變量)。全局變量可以被其他文件調用,有效範圍從定義到本源文件結束,全局變量不屬於某個函數,作用整個源程序。
p,q,c1,c2都是全局變量,全局變量的作用域從定義的時候開始生效。
當一個局部變量和全局變量的變量名相同時,會優先選擇局部變量。
int a = 11, b = 22;
int main(void){
int a = 123
小提示:
   全局變量在程序運行過程中一直佔用存儲單元。
   一般我們會把全局變量的首字母大寫(編程習慣)
   全局變量和外部變量是從不同角度提出的,全局是從作用提出的,外部是存儲方式提出的,表示生存週期。
 
2.鏈接
1)        鏈接與作用域
多個源文件編譯後,生成的目標文件以及從庫函數中引用的函數會經過鏈接器進行鏈接,生成可執行文件。爲了區分不同文件中的相同變量名是否爲同一個變量,要用變量的鏈接屬性(linkage)決定。
作用域是對文件內的變量的範圍作標識,鏈接則是在不同文件間共享。
5)        鏈接屬性(針對聲明而言的)
Ø  外部(external)  多個文件中聲明,表示同一個對象或函數。
Ø  內部(internal)  可在本文件聲明,static標示,在文件中使用。
Ø  空鏈接(none)  不同區域聲明的變量屬於獨立的對象。
6)        鏈接關鍵字:externstatic
兩個關鍵字用來修改變量的鏈接屬性。
在一個默認的外部變量前面加上static後,將鏈接屬性更改爲內部鏈接。
int g = 5;        // 文件作用域,鏈接屬性爲extern
static int do = 3;    // 文件作用域,更改爲internal
int main(int argc , char * argv[]) // static將變量do限定在了本文件,防止其                                                      他項目文件引用或造成變量重名錯誤。
Ps
static只對缺省鏈接屬性爲external的聲明纔有改變鏈接屬性的效果。
extern 關鍵字可以聲明一個變量,告訴要使用它的文件或函數,這個變量在別的文件內聲明,這裏只是引用。所以,extern擴大了一個標示符的作用域。如定義一個全局變量gm, 之後的函數可以使用gm,前面的函數就不能使用gm,加上extern 聲明擴到作用域到此函數,則可以使用。
 
C和指針(中文版)》上一道題,P49
假如想着同一個源文件裏寫兩個函數xy,需要滿足以下條件:
名字        類型       存儲類型        鏈接類型       作域                            初始值
a              int              static            external          x可以訪問,y不能           1
b              char           static            none               x,y都可以訪問                    2
c              int              automatic     none              x的局部變量                        3
d             float           static            none              x的局部變量                         4
所有初始化都在聲明中完成,而不是通過函數完成。
a鏈接屬性external,應該位於所有代碼塊外面,但由於x可以訪問,y不能,所以要出現在y函數後面,x函數前面;
b鏈接類型爲none,則應該位於代碼塊內,具有代碼塊作用域。但是由於xy都能訪問到,所以x需要調用y,或反之。。(筆者以爲,b的鏈接類型應該是internal,且位於文件起始處)
c,d明顯要處於x的代碼塊內,且一個爲static變量,一個automatic變量。
1.      static int b = 2;
2.      void y()
3.      {
4.          ...
5.      }
6.      int a =1;
7.      void x()
8.      {
9.          int c = 3;
10.      static d = 4;
11.  }
 
3.內部函數與外部函數
1)        靜態函數
在函數名前加上類型關鍵字static就會將函數標記爲內部函數,只能在本文件調用.
static int  * fun(int x, int b);  //static只改變了函數的作用域,並沒有改變出入棧。
內部函數也叫靜態函數, 一個工程多個文件時,內部函數可以很好避免不同開發人員定義函數重名的干擾。
7)        外部函數
函數首部加extern關鍵字就表示函數爲外部函數,可以供其他文件調用。
extern  int  fun(int a, int b);
extern 可以省略,編譯器會默認函數是外部函數。在調用文件中加extern對函數聲明,則表示該函數已經在別的源文件中定義過。
 
小結:存儲模型.c”--《C和指針》(中文版)
二、預處理命令
C語言中預處理功能有三種:
Ø  宏定義
Ø  條件編譯
Ø  文件包含
1.宏定義
1)        不帶參數的宏
#define 標識符  字符串
#undef  標識符
宏只是簡單替換, 不是C語句,不需要在定義後加分號
8)        帶參數的宏
#define 宏名(參數表 字符串
常用宏 putchar()/getchar
與函數區別:
  -- 宏引用佔用編譯時間,不佔運行時間;
  -- 引用宏沒有返回值。
  -- 宏名與形參無類型,只是符號代表。
4.條件編譯
#ifdef 標識符
codes1;
#else
codes2;
#endif
#ifdef 標識符
codes1;
#endif
#ifndef 標識符
codes1;
#else
codes2;
#endif
#if 表達式
codes1;
#else
codes2;
#endif
#define CPU_BIT
#ifndef CPU_BIT
#define INT_SIZE 16
#else
#define INT_SIZE 32
#endif
//前面應該有語句 #define CPU_BIT X
//X可爲任意值,也可省略,只要定義就ok。
表達式的值爲真則選擇codes1.
否則選擇codes2.
         
 
5.文件包含
#include 將一個源文件包含到本文件中。
兩種形式:
#include <filename> 編譯器從標準路徑中搜索filename.h  /usr/include
#include “filename” 從當前目錄或指定目錄到標準目錄的順序搜索。
gcc source.c  -I ./include  // -I 指定本地頭文件目錄
#include “file1.c” 在預處理階段,file.c 會被替換進主文件,編程同一個文件。編譯時不需要將file1.c一同編譯,否則會報多重定義錯誤。
思考:
怎樣防止一個頭文件被多次包含?
#ifndef __S5PC100_H__
#define __S5PC100_H__
eg
#define DEBUG_PRINT    printf(“File %s line%d:” \
”x=%d,y=%d,z=%d”,\
__FILE__,__LINE__,\
x,y,z)
//相鄰字符串常量自動連接爲一個字符串。不要在宏定義的後面加上分號。否則在某些語句中容易形成空語句。if() DEBUG_PRINT ;  else....。如果加了就是多個語句。
 
三、typedef
1.typedef定義別名
typedef  signed               char     int8_t  定義有符號八位數據類型
typedef  unsigned   short    uint16_t   short的重命名
typedef  int  INT32;
 
6.結構體重命名
typedef  struct  info
{
uint16_t  max_numb;
uint16_t  numb;
char  *title;
}Menu ;
Menu  menu1; // == struct info  menu1;定義一個struct info 的變量
Menu  *pm;  // == struct info  *pm; 定義一個指向struct info 類型的指針
 
7.數組:
typedef  int NUM[100];
NUM   array; //   int  array[100];
8.指針:
typedef  char *STRING; //STRING 爲字符串指針類型;
STRING p, s[10] // char *p指針變量,  char *s[10] 指針數組。
9.函數:
typedef  int (* POINTER)() //POINTER 爲指向函數的指針 類型。
POINTER  p1, p2;   (p1p2POINTER 類型的指針變量)
 
優點:定義別名可增加代碼的可移植性.
      typedef  int INT16; cpu位數變化後可重新定義typedef  short  INT16;
      time()函數的返回值爲time_t  (unsigned long / unsigned int)類型。
   表達式簡潔。
typdef 定義時佔用了存儲類型的位置,編譯會報錯.所以typedef不能和autoexternstaticregister一同使用。
 
eg:C程序設計》
int a[10], b[10], c[10], d[10];
typedef  int ARR[10];
ARR a, b, c, d; //後面定義就方便了很多。
 
 
四、子函數malloc申請空間返回
1.子函數返回堆空間地址
1)        通過返回值:
關注函數的定義與變量的定義:
 
9)        通過變量:
關注變量的定義、傳遞、形參的定義:
 
10.空宏、do{...}while(0);
空宏即沒有值的宏定義,如:
#define __copyright__
可以在函數程序中加上空宏來起到說明的作用,空宏會在編譯過程中替換成空;
 
do...while演變:
Ø  #define  macr1         code1;\
 code2;
if (true) macr; // 宏替換後code2將不是if後的執行語句;
Ø  #define  macr2    {code1; \
 code2;}
if (true)
macr2;   //開發者會習慣性地加上分號。
else
...;
替換後多了一個分號,else將沒有if與之匹配。(演示前先明確if..else..使用中沒有多餘分號)
 
Ø  #define MACR    do{...}while(0);
無論如何都會執行一次,完美解決以上問題。
11.sizeof(struct mytype);
1)        結構體對齊補充
linux系統中,按照當前結構體成員最大類型對齊,當超過4字節時,按照4字節對齊,不足4字節時,按照當前最大的對齊。
 
10)    嵌套結體對齊

 
結構體變量成員的偏移量必須是成員大小的整倍數。
 
12.子函數獲取局部變量地址
 
 
 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章