1. 程式人生 > >C語言再學習 -- 儲存型別關鍵字

C語言再學習 -- 儲存型別關鍵字

C語言中有 5 個作為儲存類說明符的關鍵字,分別是 auto、register、static、extern 以及 typedef。關鍵字typedef 與記憶體儲存無關,由於語法原因被歸入此類。

現在簡單瞭解一下這五個儲存類說明符的關鍵字:

說明符 auto  表明一個變數具有自動儲存時期。該說明符只能用於在具有程式碼塊作用域的變數宣告中,而這樣的變數已經擁有自動儲存時期,因此它主要用來明確指出意圖,使程式更易讀。

說明符 register也只能用於具有程式碼塊作用域的變數。它將一個變數歸入暫存器儲存類,這相當於請求將該變數儲存在一個暫存器內,以更快地存取。它的使用也使得不能獲得變數的地址

說明符 static

在用於具有程式碼塊的作用域的變數的宣告時,使該變數具有靜態儲存時期,從而得以在程式執行期間(即使在包含該變數的程式碼塊沒有執行時)存在並保留其值。變數仍具有程式碼塊作用域和空連結。static 用於具有檔案作用域的變數的宣告時,表明該變數具有內部連結。

說明符 extern表明在宣告一個已經在別處定義了的變數。如果包含 extern 的宣告具有檔案作用域,所指向的變數必須具有外部連結。如果包含 extern 的宣告具有程式碼塊作用域,所指向的變數可能具有外部連結也可能具有內部連結,這取決於該變數的定義宣告。

注意,這 5 個作為儲存類說明符的關鍵字,不可以同時出現的。

例如:  typedef static int int32  是錯誤的。

下面來一一詳細介紹:

1、auto關鍵字

auto 關鍵字在C語言中只有一個作用,那就是修飾區域性變數。 
auto 修飾區域性變數,表示這個區域性變數是自動區域性變數,自動區域性變數分配在棧上。(既然在棧上,說明它如果不初始化那麼值就是隨機的) 
平時定義區域性變數時就是定義的auto的,只是省略了auto關鍵字而已。可見,auto的區域性變數其實就是預設定義的普通的區域性變數。 即 int a = 10; 等價於 auto int a = 10;

auto 修飾區域性變數,若省去資料型別,變數預設為 int 型別

#include <stdio.h>
//auto int d;   修飾全域性變數 錯誤: 檔案作用域宣告‘d’指定了‘auto’
int main (void)
{
	auto int a = 10;  //等價於 int a = 10;
	auto b = 9;       //預設資料型別 為 int
	auto c;           //不初始化,值為隨機的

	printf ("sizeof (b) = %d\n", sizeof (b));
	printf ("c = %d\n", c);
	return 0;
}
輸出結果:
sizeof (b) = 4
c = -1217310732

2、register關鍵字

在 C 語言中的 register 修飾的變量表示將此變數儲存在CPU的暫存器中,由於CPU訪問暫存器比訪問記憶體快很多,可以大大提高運算速度。但在使用register時有幾點需要注意。

1)用register修飾的變數只能是區域性變數,不能是全域性變數。CPU的暫存器資源有限,因此不可能讓一個變數一直佔著CPU暫存器。

2)register變數一定要是CPU可以接受的值。

3)不可以用&運算子對register變數進行取址。比如 int i;(自動為auto)int *p=&i;是對的, 但register int j; int *p = &j; 是錯的,因為無法對暫存器的定址。

4)register只是請求暫存器變數,不一定能夠成功

5)隨著編譯程式設計技術的進步,在決定那些變數應該被存到暫存器中時,現在的C編譯環境能比程式設計師做出更好的決定。實際上,許多編譯程式都會忽略register修飾符,因為儘管它完全合法,但它僅僅是暗示而不是命令。

#include <stdio.h>
//register int n; 修飾全域性變數 錯誤: ‘n’的暫存器名無效

int main (void)
{
	register int i;
	//int *p = &i;  對i取地址 錯誤: 要求暫存器變數‘i’的地址。
	int tmp = 0;
	for (i = 1; i < 100; i++)
		tmp += i;
	printf ("tmp = %d\n", tmp);
	return 0;
}
輸出結果:
tmp = 4950

暫存器變數(register): 暫存器變數會盡量把變數放到暫存器(而不是棧或堆), 從而加快存取速度。下面的例子可以很好的看出:

#include <stdio.h>
#include <sys/timeb.h>
long long getSystemTime() {
    struct timeb t;
    ftime(&t);
    return 1000 * t.time + t.millitm;
}

#define TIME 1000000000

int m, n = TIME; /* 全域性變數 */
int main(void)
{   
    
    register int a, b = TIME; /* 暫存器變數 */
    int x, y = TIME;          /* 一般變數   */
    long long start = 0, end = 0;
    
    start=getSystemTime();
    for (a = 0; a < b; a++);
    end=getSystemTime();
    printf("暫存器變數用時: %lld ms\n", end - start);
    
    start=getSystemTime();
    for (x = 0; x < y; x++);
    end=getSystemTime();
    printf("一般變數用時: %lld ms\n", end - start);
    
    start=getSystemTime();
    for (m = 0; m < n; m++);
    end=getSystemTime();
    printf("全域性變數用時: %lld ms\n", end - start);

    return 0;
}
輸出結果:
暫存器變數用時: 533 ms
一般變數用時: 3513 ms
全域性變數用時: 3587 ms

3、static關鍵字

首先了解下,程序中的記憶體區域劃分
(1)程式碼區 存放程式的功能程式碼的區域,比如:函式名
(2)只讀常量區 主要存放字串常量和const修飾的全域性變數
(3)全域性區 主要存放 已經初始化的全域性變數 和 static修飾的全域性變數
(4)BSS段 主要存放 沒有初始化的全域性變數 和 static修飾的區域性變數,BSS段會在main函式執行之前自動清零
(5)堆區 主要表示使用malloc/calloc/realloc等手動申請的動態記憶體空間,記憶體由程式設計師手動申請和手動釋放
(6)棧區 主要存放區域性變數(包括函式的形參),const修飾的區域性變數,以及塊變數,該記憶體區域由作業系統自動管理

下面詳細介紹 static 關鍵字,主要有三類用法:

1)static 修飾全域性變數

static 修飾的全域性變數也叫靜態全域性變數,和已經初始化的全域性變數同 在全域性區

該類具有靜態儲存時期檔案作用域內部連結僅在編譯時初始化一次如未明確初始化,它的位元組都被設定為0。static全域性變數只初使化一次是為了防止在其他檔案單元中被引用;利用這一特性可以在不同的檔案中定義同名函式和同名變數,而不必擔心命名衝突。

示例說明:

file.h

//標頭檔案衛士
#ifndef __FILE_H__
#define __FILE_H__
void foo ();
#endif
file1.c
#include <stdio.h>
#include "file.h"

int n = 5;  //已初始化的全域性變數
static int m = 10;  //已初始化的靜態全域性變數

int x;  //未初始化的全域性變數,自動初始化為 0
static int y;  //未初始化的靜態全域性變數, 自動初始化為 0

void foo ()  //靜態全域性變數,具有檔案作用於,靜態定義,內部連結
{
	printf ("x = %d, y = %d\n", x, y);
	printf ("n = %d, m = %d\n", n, m);
}

file2.c

#include <stdio.h>
#include "file.h"
int main (void)
{
	extern int n;    
	extern int m;  
	foo ();
        printf ("n = %d\n", n);  //全域性變數,可被其他檔案使用
        //printf ("m = %d\n", m);  //靜態全域性變數, 不可被其他檔案使用
        //出現錯誤 file2.c:(.text+0x27): undefined reference to `m'
 return 0;
}
輸出結果:
編譯:
gcc file1.c file2.c -o file
輸出結果:
x = 0, y = 0
n = 5, m = 10
n = 5

2)static 修飾區域性變數

static 修飾的區域性變數也叫靜態區域性變數,和沒有初始化的全域性變數同 BBS段。而非靜態區域性變數是被分配在上面的。非靜態區域性變數,函式呼叫結束後儲存空間釋放靜態區域性變數,具有靜態儲存時期。只在程式開始時執行一次,函式呼叫結束後儲存區空間並不釋放,保留其當前值。

該類具有靜態儲存時期程式碼作用域空連結僅在編譯時初始化一次如未明確初始化,它的位元組都被設定為0

file.h

//標頭檔案衛士
#ifndef __FILE_H__
#define __FILE_H__
void foo ();
#endif
file1.c
#include <stdio.h>
#include "file.h"
void foo ()
{
	int n = 5;     //已初始化,區域性變數
	static m = 10; //已初始化,靜態區域性變數

	printf ("n = %d, m = %d\n", n, m);	

	n++;
	m++;
}

file2.c

#include <stdio.h>
#include "file.h"
int main (void)
{
	foo ();
	foo ();
	foo ();  //自動區域性變數,函式呼叫結束後儲存空間釋放
	foo ();  //靜態區域性變數,具有靜態儲存時期。只在程式開始時執行一次,函式呼叫結束後儲存區空間並不釋放,保留其當前值。

	extern int n;
	extern int m;
//	printf ("n = %d\n", n);  
//	printf ("n = %d\n", m);  //靜態區域性變數,為空連結,不可以被其他檔案使用,出現錯誤
//      file2.c:(.text+0x1f): undefined reference to `n'
//      file2.c:(.text+0x36): undefined reference to `m'

        int x;  //未初始化,區域性變數,初始化為隨機數
        static int y; //未初始化,靜態區域性變數,自動初始化為 0
        printf ("x = %d, y = %d\n", x, y);

        return 0;
}
輸出結果:
編譯:
gcc file1.c file2.c -o file
輸出結果:
n = 5, m = 10
n = 5, m = 11
n = 5, m = 12
n = 5, m = 13
x = -1216741388, y = 0

3)static 修飾函式

外部函式可被其他檔案中的函式呼叫,而靜態函式只可以在定義它的檔案中使用。例如,考慮一個包含如下函式宣告的檔案:

  1. double gamma (); //預設為外部的
  2. staticdouble beta (); //靜態函式
  3. externdouble delta ();  
函式gamma ()和delta ()可被程式的其他檔案中的函式使用,而beta ()則不可以,因為beta ()被限定在一個檔案內,故可在其他檔案中使用相同名稱的不同函式。使用 static 儲存類的原因之一就是建立為一個特定模組所私有的函式,從而避免可能的名字衝突。

通常使用關鍵字 extern 來宣告在其他檔案中定義的函式。這一習慣做法主要是為了程式更清晰,因為除非函式宣告使用了關鍵字 static ,否則認為就是extern 的。

示例:

file.h

//標頭檔案衛士
#ifndef __FILE_H__
#define __FILE_H__
void call (void);
static void foo (void);
#endif

file1.c
#include <stdio.h>
#include "file.h"

//靜態函式,不能被其他檔案使用
static void foo (void)
{
	printf ("foo\n");
}

void call (void)
{
	foo ();
}

file2.c

#include <stdio.h>
#include "file.h"

//使用相同名字的不同函式
void foo (void)
{
    printf ("hello world\n");
}
int main (void)
{
	call ();
//	foo (); 錯誤: file2.c:(.text+0xc): undefined reference to `foo'
        foo ();
	return 0;
}
輸出結果:
編譯:
gcc file1.c file2.c -o file
輸出結果:
foo
hello world

4、extern 關鍵字

整理了好久, extern 算是最讓我糾結的了。看了好多篇文章,都沒有講出個所以然來,搞得我好鬱悶。這也體現出很有必要詳細講解下的它的用法了。

首先,再講解之前先要了解下,宣告和定義的區別。

舉個例子:
A)int i;
B)extern int i; 
哪個是定義?哪個是宣告?或者都是定義或者都是宣告?
什麼是定義:所謂的定義就是(編譯器)建立一個物件,為這個物件分配一塊記憶體並給它取上一個名字,這個名字就是我們經常所說的變數名或物件名。但注意,這個名字一旦和這塊記憶體匹配起來,它們就同生共死,終生不離不棄。並且這塊記憶體的位置也不能被改變。一個變數或物件在一定的區域內(比如函式內,全域性等)只能被定義一次如果定義多次,編譯器會提示你重複定義同一個變數或物件。
什麼是宣告:有兩重含義,如下:
第一重含義:告訴編譯器,這個名字已經匹配到一塊記憶體上了,下面的程式碼用到變數或物件是在別的地方定義的。宣告可以出現多次。
第二重含義:告訴編譯器,我這個名字我先預定了,別的地方再也不能用它來作為變數名或物件名。比如你在圖書館自習室的某個座位上放了一本書,表明這個座位已經有人預訂,別人再也不允許使用這個座位。其實這個時候你本人並沒有坐在這個座位上。這種宣告最典型的例子就是函式引數的宣告,例如:
void fun(int i, char c);
好,這樣一解釋,我們可以很清楚的判斷: A)是定義; B)是宣告。
記住, 定義宣告最重要的區別:定義建立了物件併為這個物件分配了記憶體,宣告沒有分配記憶體。
宣告: 指定了一個變數的識別符號,用來描述變數的型別,是型別還是物件,或者函式等。宣告,用於編譯器(compiler)識別變數名所引用的實體。以下這些就是宣告:
extern int bar;
extern int g(int, int);
double f(int, double); // 對於函式宣告,extern關鍵字是可以省略的。
class foo; // 類的宣告,前面是不能加class的。
定義: 是對宣告的實現或者例項化。聯結器(linker)需要它(定義)來引用記憶體實體。與上面的宣告相應的定義如下:
int bar;
int g(int lhs, int rhs) {return lhs*rhs;} 
double f(int i, double d) {return i+d;} 
class foo {};// foo 這裡已經擁有自己的記憶體了,對照上面兩個函式,你就應該明白{}的用處了吧?
無論如何,定義 操作是隻能做一次的。如果你忘了定義一些你已經宣告過的變數,或者在某些地方被引用到的變數,那麼,聯結器linker是不知道這些引用該連線到那塊記憶體上的。然後就會報missing symbols 這樣的錯誤。如果你定義變數超過一次,聯結器是不知道把引用和哪塊記憶體連線,然後就會報 duplicated symbols這樣的錯誤了。以上的symbols其實就是指定義後的變數名,也就是其標識的記憶體塊。
總結:
如果只是為了給編譯器提供引用標識,讓編譯器能夠知道有這個引用,能用這個引用來引用某個實體(但沒有為實體分配具體記憶體塊的過程)是為宣告。如果該操作能夠為引用指定一塊特定的記憶體,使得該引用能夠在link階段唯一正確地對應一塊記憶體,這樣的操作是為定義宣告是為了讓編譯器正確處理對宣告變數和函式的引用定義是一個給變數分配記憶體的過程,或者是說明一個函式具體幹什麼用。
通過上述對宣告和定義的解釋可以看出,在C語言中,修飾符 extern 用在變數或者函式的宣告前,用來說明“此變數/函式是在別處定義的,要在此處引用”。extern 是 C/C++ 語言中表明函式和全域性變數作用範圍(可見性)的關鍵字,該關鍵字告訴編譯器,其宣告的函式和變數可以在本模組或其它模組中使用
1)extern 修飾變數的宣告
具有外部連結的靜態變數具有檔案作用域,外部連結和靜態儲存時期。這一型別有時被稱為外部儲存類,這一型別的變數被稱為外部變數。把變數的定義宣告放在所有函式之外,即建立了一個外部變數。為了使程式更加清晰,可以在使用外部變數的函式中通過使用 extern 關鍵字來再次宣告它。如果變數是在別的檔案中定義,使用 extern 來宣告該變數就是必須的。
int n;   /*外部定義的變數*/  
double Up[100];  /*外部定義的陣列*/  
extern char Coal;  /*必須的宣告,因為Coal在其他檔案中定義*/  
  
void next (void);   
  
int main (void)  
{  
    extern double Up[]; /*可選的宣告,此處不必指明陣列大小*/  
    extern int n;  /*可選的宣告,如果將extern漏掉,就會建立一個獨立的自動變數*/  
}  
  
void next (void)  
{  
    ...  
}  
下列 3 個例子展示了外部變數和自動變數的 4 種可能組合:
/*例1*/  
int H;  
int magic ();  
int main (void)  
{  
    extern int H;  /*宣告H為外部變數*/  
    ...  
}  
  
int magic ()  
{  
    extern int H;  /*與上面的H是同一變數*/  
}  
  
/*例2*/  
int H;  
int magic ();  
int main (void)  
{  
    extern int H;  /*宣告H為外部變數*/  
    ...  
}  
  
int magic ()  
{  
    ...            /*未宣告H,但知道該變數*/  
}  
  
  
/*例3*/  
int H; /*對main()和magic()不可見,但是對檔案中其他不單獨擁有區域性H的函式可見*/  
int magic ();  
int main (void)  
{  
    int H;          /*宣告H, 預設為自動變數,main()的區域性變數*/  
    ...  
}  
  
int P;/*對magic()可見,對main()不可見,因為P宣告子啊main()之後*/  
int magic ()  
{  
    auto int H;     /*把區域性變數H顯式地宣告為自動變數*/  
}  
這些例子說明了外部變數的作用域從宣告的位置開始到檔案結尾為止。它們也說明了外部變數的生存期

外部變數H和P存在的時間與程式執行時間一樣,並且它們不侷限於任一函式,在一個特定函式返回時並不消失。

多檔案的程式中宣告外部變數,使用 extern 來宣告該變數就是必須的。注意能夠被其他模組以extern修飾符引用到的變數通常是全域性變數,可以放在file2.c檔案的任何位置

//file1.c
int n = 10, m = 5; //n, m 為全域性變數,只能定義在一處
//file2.c
#include <stdio.h>
//extern int n, m;  //宣告 全域性變數
//int n= 2, m = 3;  
/*
若果再次定義n,m。會出現錯誤
/tmp/cc4R2MbY.o:(.data+0x0): multiple definition of `n'
/tmp/ccwV9hWd.o:(.data+0x0): first defined here
/tmp/cc4R2MbY.o:(.data+0x4): multiple definition of `m'
/tmp/ccwV9hWd.o:(.data+0x4): first defined here
collect2: ld 返回 1
*/

void max (void);

int main (void)
{
	//printf ("n = %d, m = %d\n", n, m);
	max ();
	return 0;
}

void max (void)
{
	extern int n, m; //n, m為全域性變數
	printf ("n = %d, m = %d\n", n, m);
}
編譯:
gcc file1.c file2.c -o file
輸出結果:
n = 10, m = 5

2)extern 修飾函式的宣告

外部函式可被其他檔案中的函式呼叫,而靜態函式只可以在定義它的檔案中使用。例如,考慮一個包含如下函式宣告的檔案:

  1. double gamma (); //預設為外部的
  2. staticdouble beta (); //靜態函式
  3. externdouble delta ();  
函式gamma ()和delta ()可被程式的其他檔案中的函式使用,而beta ()則不可以,因為beta ()被限定在一個檔案內,故可在其他檔案中使用相同名稱的不同函式。使用 static 儲存類的原因之一就是建立為一個特定模組所私有的函式,從而避免可能的名字衝突。

通常使用關鍵字 extern 來宣告在其他檔案中定義的函式。這一習慣做法主要是為了程式更清晰,因為除非函式宣告使用了關鍵字 static ,否則認為就是extern 的。換句話說,在定義(函式)的時候,這個extern居然可以被省略。

如果函式的宣告中帶有關鍵字 extern,僅僅是暗示這個函式可能再別的原始檔裡定義,沒有其它作用。即下述這兩個函式宣告沒有明顯的區別:extern int foo (); 和 int foo (); 函式定義和宣告時 extern 可有可無
//file.c
#include <stdio.h>

void foo (void)
{
	printf ("hello world!\n");
}
//file2.c
#include <stdio.h>

extern void foo ( );  //該函式宣告可以放在 file2.c的任何位置
//等同於 void foo ();
int main (void)
{
	foo ();
	return 0;
}
編譯:
gcc file1.c file2.c -o file
輸出結果:
hello world!
一般把所有的全域性變數和全域性函式都放在一個 *.c 檔案中,然後用一個同名的 *.h 檔案包含所有的函式和變數的宣告.
//main.c
#include <stdio.h>
#include "read.h"

int main (void)
{
	read ();
	printf ("num = %d\n", num);
	return 0;
}
//read.c
#include <stdio.h>
#include "read.h"
int num; //全域性變數  定義

void read (void)
{
	printf ("請輸入一個數字:");
	scanf ("%d", &num);
}
//read.h
//標頭檔案衛士,防止標頭檔案被重複定義
#ifndef __READ_H__
#define __READ_H__
extern int num;
void read (void);  //等價於 extern void read (void);
#endif
編譯:
gcc main.c read.c -o read
輸出結果:
請輸入一個數字:12
num = 12
注意:
(1) extern int num = 10;  沒有這種形式,不是定義。如果在 read.h中如此寫的話會出現:
read.h:3:12: 警告: ‘num’已初始化,卻又被宣告為‘extern’ [預設啟用]
In file included from read.c:2:0:
read.h:3:12: 警告: ‘num’已初始化,卻又被宣告為‘extern’ [預設啟用]
/tmp/ccQ3Jzzm.o:(.data+0x0): multiple definition of `num'
/tmp/cceLUBvB.o:(.data+0x0): first defined here
collect2: ld 返回 1
(2)再有,在使用 extern 時候要嚴格對應宣告時的格式,例如:
宣告的函式為: extern void read (void);
定義的時候 返回值、形參型別、函式名 需要一致,為 void read (void) {...}
C 程式中,不允許出現型別不同的同名變數。
(3)定義陣列,修飾指標
在一個原始檔裡定義了一個數組:char a[100];
在另外一個檔案裡用下列語句進行了宣告:extern char *a;
這樣是不可以的,程式執行時會告訴你非法訪問。原因在於,指向型別T的指標並不等價於型別T的陣列。extern char *a宣告的是一個指標變數而不是字元陣列,因此與實際的定義不同,從而造成執行時非法訪問。應該將宣告改為extern char a[ ]。
但是,extern char a[]與 extern char a[100]等價。
因為這只是宣告,不分配空間,所以編譯器無需知道這個陣列有多少個元素。
3)extern "C"
上面講到了,C 程式中,不允許出現型別不同的同名變數。例如:
#include <stdio.h>

void foo(void);

int foo (int ,int);

int main (void)
{
	return 0;
}
輸出結果:
test.c:5:5: 錯誤: 與‘foo’型別衝突
test.c:3:6: 附註: ‘foo’的上一個宣告在此
C++程式中 卻允許出現過載過載的定義同一個作用域,函式名相同,引數表不同的函式構成過載關係,例如:
//renam.cpp
#include <iostream>
using namespace std;
void foo (int i) {
	cout << i << endl;
}
void foo (int i, double d) {
	cout << i << ' ' << d << endl;
}
int main (void) {
	foo (1); //_Z3fooi (1);
	foo (1,2);//_Z3fooid (1, 2.);
	return 0;
}
gcc -c rename.cpp  //生成 rename.o 

nm rename.o //檢視
=============================
000000f3 t _GLOBAL__I__Z3fooi
00000000 T _Z3fooi
0000002b T _Z3fooid
000000b3 t _Z41__static_initialization_and_destruction_0ii
         U _ZNSolsEPFRSoS_E
         U _ZNSolsEd
         U _ZNSolsEi
         U _ZNSt8ios_base4InitC1Ev
         U _ZNSt8ios_base4InitD1Ev
         U _ZSt4cout
         U _ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_
00000000 b _ZStL8__ioinit
         U _ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_c
         U __cxa_atexit
         U __dso_handle
         U __gxx_personality_v0
00000081 T main
可以看到:函式被 C++編譯後在庫中的名字與 C 語言的不同。
函式void foo (int i); 的庫名為
_Z3fooi
函式void foo (int i, double d);  的庫名為 _Z3fooid
通過庫名,可以看出來包含了函式名、函式引數數量及型別資訊,C++就是靠這種機制來實現函式過載的。而 C 語言則不會,因此會造成連結時找不到對應函式的情況,此時C函式就需要用extern “C”進行連結指定,來解決名字匹配問題,這告訴編譯器,請保持我的名稱,不要給我生成用於連結的中間函式名。
未加 extern "C" 宣告的,在C++中因為過載,庫名是 _Z3fooid,加上 extern "C" 會採用 C語言的方式 編譯生成 foo。extern “C”這個宣告的真實目的是為了實現C++與C及其它語言的混合程式設計
C++中 extern "C" 的兩種用法:
1)用C++語言寫的一個函式,如果想讓這個函式可以被其他C語言程式所用,則用extern "C" 來告訴C++編譯器,請用C語言習慣來編譯此函式。如:
//add.h
#ifndef _ADD_H
#define _ADD_H
#ifdef __cplusplus
extern "C" {
#endif
int add (int ,int );
#ifdef __cplusplus
}
#endif
#endif
//add.cpp
#include "add.h"
int add (int x, int y) {
	return x + y;
}
//main.c
#include <stdio.h>
#include "add.h"
int main (void) {
	int x=13,y=6;
	printf("%d+%d=%d\n",x,y,add(x,y));
	return 0;
}
編譯:
gcc add.cpp main.c -o add -lstdc++
輸出結果:
13+6=19
__cplusplus是cpp中自定義的一個巨集,告訴編譯器,這部分程式碼按C語言的格式進行編譯,而不是C++的
原始檔為*.c,__cplusplus沒有被定義,extern "C" {}這時沒有生效對於C他看到只是 extern int add(int, int); 
add 函式編譯符號成 add
gcc -c main.c 
nm main.o 
         U add
00000000 T main
         U printf
原始檔為*.cpp(或*.cc,*.C,*.cpp,*.cxx,*.c++), __cplusplus被定義 ,對於C++他看到的是 extern "C"  { extern  int add( int ,int);}編譯器就會知道 add(13, 6);呼叫的C風格的函式,就會知道去找add符號而不是_Z3addii ;因此編譯正常通過。
注:-lstdc++ 申明用c++庫
如果將,add.h 如下改寫,不使用 extern "C":
#ifndef _ADD_H
#define _ADD_H
/*
#ifdef __cplusplus
extern "C" {
#endif
int add (int ,int );
#ifdef __cplusplus
}
#endif
*/
extern int add (int, int);
#endif
編譯:gcc add.cpp main.c -o add -lstdc++  出現錯誤
/tmp/ccBSzdDa.o: In function `main':
main.c:(.text+0x29): undefined reference to `add'
collect2: ld 返回 1
但是,編譯:g++ add.cpp main.c -o add 是OK的
因為g++會自動將c的模組中的符號表轉換為 _Z3addii 這也是GNU compiler的強大之處,可是別的編譯器也許就不這麼智慧了。所以在c/c++混合程式設計時還是最好加上extern “C”。

2)如果要在C++程式中呼叫C語言寫的函式, 在C++程式裡邊用 extern "C" 修飾要被呼叫的這個C程式,告訴C++編譯器此函式是C語言寫的,是C語言編譯器生成的,呼叫他的時候請按照C語言習慣傳遞引數等。
//sub.h
#ifndef _SUB_H
#define _SUB_H
int sub(int ,int);
#endif
//sub.c
#include "sub.h"
int sub(int x,int y) {
	return x + y;
}
//main.cpp
#include <iostream>
using namespace std;
extern "C" {
#include "sub.h"
}
int main (void) {
	int x=5,y=6;
	cout << x << "+" << y << "="
		<< sub(x, y) << endl;
	return 0;
}
編譯:
gcc sub.c main.cpp -o sub -lstdc++
5+6=11


相關推薦

C語言學習 -- 儲存型別關鍵字

C語言中有 5 個作為儲存類說明符的關鍵字,分別是 auto、register、static、extern 以及 typedef。關鍵字typedef 與記憶體儲存無關,由於語法原因被歸入此類。現在簡單瞭解一下這五個儲存類說明符的關鍵字:說明符 auto  表明一個變數具有自

C語言學習 -- 關鍵字struct(轉)

結構體的一般定義形式為: 標籤(tag)欄位允許為成員列表提供一個名字,這樣它就可以在後續的宣告中使用。標籤允許多個宣告使用同一個成員列表,並且建立同一種類型的結構。 struct 標籤{ 型別名1 成員名1; 型別名2 成員名2; …… 型別名n 成員名n;    }結構體變數;

C語言學習--關鍵字

C語言一共有32個關鍵字,如下表所示: 關鍵字 說明 auto 宣告自動變數 short 宣告短整型變數或函式 int

C語言學習 -- 關鍵字typedef

一、typedef 介紹 typedef為C語言的關鍵字,作用是為一種資料型別定義一個新名字。比如人們常常使用 typedef 來編寫更美觀和可讀的程式碼。所謂美觀,意指 tepe

C語言學習 -- 關鍵字volatile

上週確實事情挺多的,年會、公司聚餐,一到過年就有忙不完的事分心。還好C語言再學習總結的已經差不多了,年前也不展開別的了,接下來這十幾天、總結幾篇典型的面試題吧。 言歸正傳,接下來看看關鍵字 volatile。 一、volatile 介紹 Indicates that

C語言學習-定義變數

當我們在c語言裡建立一個變數的時候 int x = 5; int y = 6; 00C517B8 mov dword ptr [x],5 00C517BF mov dword ptr [y],6 實際上在彙編層面,

C語言學習5-陣列與優化

什麼是陣列?為什麼要用陣列? 通俗來講,在記憶體中一塊連續儲存資料的叫陣列,陣列的每個子元素的寬度都一樣,並且只能為通用的資料型別做單位(char,short,int等等) 讓我們先定義一個數組,然後賦值: char arr1[2] = { 0 }; arr1

C語言學習7-結構體

為什麼使用結構體? struct My { char name[20] = "如風斬嶽"; int age; char addr[50] ; int money; double Coordinates; //..... }; 當我們有這樣一種需求,

C語言學習 -- 負數

有符號數的表示方法是由硬體決定,而不是由C決定的。有三種表示方法: 1、二進位制原碼 0000 0001  表示 1 1000 0001  表示 -1 這個方法有個缺點是有兩個零: +0 和 -0。這會引起混淆,而且用兩個位

C語言學習 -- 檔案

檔案是什麼 一個檔案(file)通常就是磁碟上的一段命名的儲存區。C 將檔案看成是連續的位元組序列,其中每一個位元組都可以單獨地讀取。 二進位制和文字模式 1、在windows系統中,文字模式

C語言學習 -- ASCII碼錶(轉)

ASCII碼錶第一部分:ASCII非列印控制字元表ASCII表上的數字0–31分配給了控制字元,用於控制像印表機等一些外圍裝置。例如,12代表換頁/新頁功能。此命令指示印表機跳到下一頁的開頭。(參詳ASCII碼錶中0-31)第二部分:ASCII列印字元數字 32–126 分配給了能在鍵盤上找到的字元,當您檢視

【 分類 】- C語言學習

專欄達人 授予成功建立個人部落格專欄

C語言學習 -- 詳解C++/C 面試題 1

對這篇文章記憶猶新,因為之前找工作面試的時候,遇到過一家公司就是用的這套面試題。現在就結合考查的知識點和我總結完 C 語言再學習後的深入理解,來詳細的講講我對這篇文章的總結。 一、請填寫BOOL ,

C語言學習 -- 論記憶體管理

但現在看來,缺少示例。從新再寫一篇文章,著重介紹常見記憶體錯誤、跨函式使用儲存區。開始吧,再論記憶體管理!!發生記憶體錯誤是件非常麻煩的事情。編譯器不能自動發現這些錯誤,通常是在程式執行時才能捕捉到。而這些錯誤大多沒有明顯的症狀時隱時現增加了改錯的難度。一、常見的記憶體錯誤及

C語言學習 -- 記憶體管理

malloc ( )函式: malloc ( ) 向系統申請分配指定size個位元組的記憶體空間。返回型別是 void* 型別。void* 表示未確定型別的指標。C,C++規定,void* 型別可

C語言學習 -- 時間函式

gmtime函式:可以把time函式得到的結果按照格林尼治時間轉換成一個結構體localtime函式:可以把time函式得到的結果按照當前時區轉換成一個結構體asctime函式:可以把一個記錄時間的結構體轉換成字串,一般與上兩個函式合用的格林時間,與北京時間換算,/* 時間函式演示 */ #

C語言學習 -- 論陣列和指標

之前有總結指標陣列,但是現在看來總結的太簡單了。好多重要的知識點都是一帶而過的。本想在後面新增後來想想算了,還是再寫一篇文章來詳細介紹陣列和指標這對冤家吧。一開始覺得C語言再學習專欄都寫了五十篇了,現在

C語言學習 -- NUL和NULL的區別

NUL 是ASCII 字符集中 '\0' 字元的名字,它的位元組模式為全 0。NULL 指一個其值為 0 的指標。它們都是整型值,其值也相同,所以它們可以互換使用。然而,你還是應該使用適當的常量,因為

C語言學習 -- 段錯誤(核心已轉儲)

一、什麼是段錯誤?一旦一個程式發生了越界訪問,cpu 就會產生相應的保護,於是 segmentation fault 就出現了,通過上面的解釋,段錯誤應該就是訪問了不可訪問的記憶體,這個記憶體區要麼是不存在的,要麼是受到系統保護的,還有可能是缺少檔案或者檔案損壞。二、段錯誤產

C語言學習 -- 詳解C++/C 面試題 2

(經典)C語言測試:想成為嵌入式程式設計師應知道的0x10個基本問題。1、用預處理指令#define 宣告一個常數,用以表明1年中有多少秒(忽略閏年問題) #define SENCONDS_PER_YE