2024-07-22|閱讀時間 ‧ 約 30 分鐘

[C]static修飾


拜託,注意一下變數的作用域,都出來工作了,別像國中生寫程式一樣


1.修飾局部變數


先看如果沒有使用static修飾時的情況

給定一程式

#include <stdio.h>
#include <stdlib.h>

void func(void)
{
int x = 0 ;

x = x + 1 ;

printf("%p -> %d \\n", &x , x );
}
int main()
{
func();
func();
func();
exit(0);
}

執行結果:

0x7ffc766476c4 -> 1
0x7ffc766476c4 -> 1
0x7ffc766476c4 -> 1

可以看到重複執行func()輸出結果都是1

因為每次的func( )中的int x = 0 皆會重複將x定義為0

之後再+1

另外雖然印出來的地址相同且變數相同

x變數實際上每次都會在函式結束時銷毀,下次執行時再被定義

所以實際上三個是不同的變數

若用古老一點的gcc版本,每次印出來就是三組不同的記憶體位址了

若將func()中的i使用static修飾:

void func(void)
{
static int x = 0 ;

x = x + 1 ;

printf("%p -> %d \\n", &x , x );
}

執行結果:

0x55ed3fc40014 -> 1
0x55ed3fc40014 -> 2
0x55ed3fc40014 -> 3

可以發現static int x = 0 ; 僅僅被定義了一次

後續重複執行func時則會根據x的初始值進行累加

此時無論你使用何種gcc的版本,記憶體位址印出來的絕對會是同一塊地址

這裡可以簡單理解為:static修飾的變量具繼承性,且在文件中只被定義一次



2.修飾全局變數


在一些稍具規模的專案中最常使用的情況

給定三個檔案文件,主文件main.c 以及定義函式的func.c , func.h

func.c :

#include <stdio.h>
#include <stdlib.h>

#include "func.h"

static int i = 100;

void func (void)
{

printf("[%s] : i = %d\\n" , __FUNCTION__ , i);

exit(0);
}
/* 實現func() 作用為印出i值 */

func.h :

#ifndef FUNC_H___
#define FUNC_H___

void func(void);/* 定義func() */

#endif

main.c :

#include <stdio.h>
#include <stdlib.h>

#include "func.h"

static int i = 10 ;

int main()
{
printf("[%s] : i = %d\\n" , __FUNCTION__ , i);/* 印出i值 */

func();/* 呼叫func() */

exit(0);
}

其中__FUNCTION__ 為gcc預定義的巨集,可以輸出當前函式名稱

輸出結果:

[main] : i = 10
[func] : i = 100

可以看到透過static,使兩個文件的變數i分別獨立,生命週期僅涵蓋當前文件

main函數中的printf印出的當前文件的i=10

而之後使用的func()則是使用func.c中定義的i=100




3.全局變數-沒有static的話?


接著剛剛的程式碼

假設將func.c以及main.c中的i的前方static拿掉

在進行編譯時,會顯示下列錯誤訊息:

/usr/bin/ld: /tmp/ccjtT5QF.o:(.data+0x0): multiple definition of `i'; 
/tmp/cc2KBh7e.o:(.data+0x0): first defined here
collect2: error: ld returned 1 exit status

看到了訊息中的multiple definition of `i',表示我們對i的定義重複且發生衝突了

使用static修飾即是防止對象向外擴展,而造成這種編譯錯誤

因此在大型專案的整合上,變數的作用域要小心且嚴謹的檢查


「欸欸欸我跑自己的程式就編譯成功,怎麼跟你們的合一起就不行?你們寫錯了吧?」 by 三周後被資遣雷包工程師-(真人真事🙃)


4.用static修飾函數與簡單封裝


static 亦可以用於修飾函數

不更改main.c

func.c以及func.h中將func()static修飾-

func.c :

#include <stdio.h>
#include <stdlib.h>

#include "func.h"

static int i = 100;

static void func (void)
{

printf("[%s] : i = %d\\n" , __FUNCTION__ , i);

exit(0);
}
/* 實現func() 作用為印出i值 */

func.h :

#ifndef FUNC_H___
#define FUNC_H___

static void func(void);/* 定義func() */

#endif

此時func()已經被staic修飾而無法被main.c使用

因此編譯時gcc會跳出以下訊息:

func.h:4:13: warning: ‘func’ used but never defined
4 | static void func(void);
| ^~~~
/usr/bin/ld: /tmp/ccqYGJSq.o:(.data+0x0): multiple definition of `i'; /tmp/cchlq9eW.o:(.data+0x0): first defined here
/usr/bin/ld: /tmp/cchlq9eW.o: in function `main':
main.c:(.text+0x2f): undefined reference to `func'
collect2: error: ld returned 1 exit status

其中顯示了main.c無法引用到func()

func()static修飾後僅在func.cfunc.h 定義

因此static 的修飾防止了func()的定義外擴

如何使main.c使用func()的功能呢?

由於此時我們使用static 修飾func()來防止外擴

因此我們另外創建一個call_func()來提供main.c使用:

func.c :

#include <stdio.h>
#include <stdlib.h>

#include "func.h"

static int i = 100;

static void func (void)
{

printf("[%s] : i = %d\\n" , __FUNCTION__ , i);

exit(0);
}
/* 實現func() 作用為印出i值 */

void call_func(void)
{
func();
}
/* 實現call_func() 作用為執行func() */

func.h :

#ifndef FUNC_H___
#define FUNC_H___

static void func(void);/* 定義func() */
void call_func(void);/* 定義call_func() */
#endif

main.c :

#include <stdio.h>
#include <stdlib.h>

#include "func.h"

static int i = 10 ;

int main()
{
printf("[%s] : i = %d\\n" , __FUNCTION__ , i);/* 印出i值 */

call_func();/* 執行call_func() */

exit(0);
}

執行結果:

[main] : i = 10
[func] : i = 100

如此可以保證函式定義不外擴的同時

使外部的main.c使用func()的功能

透過修飾可以在C語言中對函式進行簡單的封裝




5.後記


在此只整理c語言的用法

在c++的環境static更可以用來修飾class內的成員

而善用static至少有兩個明顯的好處:

  • 避免全域變數在其他文件中可見,減少衝突跟不必要的依賴
  • 減少了不必要的初始化


分享至
成為作者繼續創作的動力吧!
© 2024 vocus All rights reserved.