一、存儲模型
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. 修飾全局變量,使其只能在本文件可見
注意到,static在C語言中的三個作用,其中第1和第3個作用其實都是一樣的,改變的是函數或者變量的可見範圍。
只有當用static來修飾局部變量的時候,它的作用纔是代表一個存儲區域。
2)
register
請求編譯器將變量保存在寄存器中,從而加快程序的運行,對於頻繁使用的變量,存放在寄存器中可以減少和內存讀取(程序中遇到變量時用控制器發指令將變量的值送到運算器中,需要存數再保存到內存中)。
register只能對局部變量和函數形參聲明(全局變量不可申請register類型)。???
register不能取址,
register int j; int *p=&j;是不允許的,因爲無法對寄存器定製。
register申請存放在寄存器時,不一定成功,因爲寄存器數量有限,
在申請失敗時,會默認成auto類型。
cpu的寄存器對數據類型有限制,不是所有類型變量都可以申請爲寄存器類型。如:有些系統只支持將int、char和指針變量定義爲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)
鏈接關鍵字:extern、static
兩個關鍵字用來修改變量的鏈接屬性。
在一個默認的外部變量前面加上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:
假如想着同一個源文件裏寫兩個函數x,y,需要滿足以下條件:
名字
類型
存儲類型 鏈接類型
作域 初始值
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,則應該位於代碼塊內,具有代碼塊作用域。但是由於x,y都能訪問到,所以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; (p1、p2爲POINTER
類型的指針變量)。
優點:定義別名可增加代碼的可移植性.
typedef int INT16;
在cpu位數變化後可重新定義typedef short INT16;
time()函數的返回值爲time_t (unsigned long / unsigned int)類型。
表達式簡潔。
typdef 定義時佔用了存儲類型的位置,編譯會報錯.所以typedef不能和auto、extern、static、register一同使用。
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.子函數獲取局部變量地址