国产片侵犯亲女视频播放_亚洲精品二区_在线免费国产视频_欧美精品一区二区三区在线_少妇久久久_在线观看av不卡

服務器之家:專注于服務器技術及軟件下載分享
分類導航

PHP教程|ASP.NET教程|Java教程|ASP教程|編程技術|正則表達式|C/C++|IOS|C#|Swift|Android|VB|R語言|JavaScript|易語言|vb.net|

服務器之家 - 編程語言 - C/C++ - C++面試基礎之static關鍵字詳解

C++面試基礎之static關鍵字詳解

2021-07-24 15:50riccoqu C/C++

這篇文章主要給大家介紹了關于C++面試基礎之static關鍵字的相關資料,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面來一起學習學習吧

前言

static是 c++ 的關鍵字,顧名思義是表示靜態的含義。它在 c++ 中既可以修飾變量也可以修飾函數。那當我們使用 static 時,編譯器究竟做了哪些事情呢?

早先面試中被問到 static 關鍵字,感覺既熟悉又陌生。熟悉是都知道如何去使用它,陌生又來自不知道它究竟對我們程序做了什么。今天就來好好復習下這個關鍵字,本文的重點也在第三部分。

先看一下示例代碼:

test1.cpp

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include <iostream>
extern int a_int;
extern void func2();
 
static char c_array[10000];
 
void func1() {
 static int a_tmp = 0;
 std::cout << a_tmp++ << std::endl;
 return;
}
 
int main(int argc, char **argv) {
 a_int = 1;
 
 //靜態局部變量示例
 for (auto i = 0; i < 5; i++) {
 func1();
 }
 
 //比較靜態全局變量的地址示例
 std::cout << static_cast<const void *>(c_array) << std::endl;
 func2();
 return 0;
}

test2.cpp

?
1
2
3
4
5
6
7
8
#include <iostream>
 
int a_int;
static char c_array[1000];
void func2() {
 std::cout << static_cast<const void *>(c_array) << std::endl;
 return 0;
}

1 先說說 extern

extern 關鍵字用于告訴編譯器,在其他的模塊中尋找相應的定義

為什么 static 前要先說 extern 呢?因為他們就像相互對立的一對關鍵字,所以 extern 與 static 一起用時編譯器會報錯~

1.1 extern 用于修飾變量

以示例代碼中的 a_int 變量為例,假設其他的變量和函數不存在

我們先 將 extern 關鍵字去掉(test1.cpp:2) ,然后執行步驟:

  • 編譯g++ -c -o test1.o test1.cpp
  • 查看符號nm test1.o

00000000000000d0 S _a_int

可以看到 a_int 為一個未初始化的符號。說明符號在 test1.o 中已經被定義了。此時直接編譯( g++ -o test1 test1.cpp )是不會報錯的。

然后我們再 將 extern 關鍵字加上(test1.cpp:2) ,并重復上面步驟觀察符號

nm test1.o

會發現 test1.o 中沒有該符號的定義。并且再編譯會報錯:

Undefined symbols for architecture x86_64:
  "_a_int", referenced from:
      _main in test1-ed3c01.o
ld: symbol(s) not found for architecture x86_64

很明顯,鏈接器沒有找到 a_int 的定義。此時只需要將 test2.o 加入再編譯(g++ -o test test1.cpp test2.cpp)就可以啦

注:此時如果去掉 main 函數中對 a_int 變量的引用,也可以編譯通過,畢竟 a_int 在程序中實際沒有用到

1.2 extern 用于修飾函數

以示例代碼中的 func2() 函數為例

因為在 main 函數中調用了 func2(),所以需要在 main 之前進行函數聲明。 但此時的函數聲明無論加不加 extern 其實 并無多少區別

1.3 extern 用于指定編譯類型

因為 C++ 編譯時會進行 name mangling[wiki] ,導致所看到的函數與實際編譯后的符號差距很大。在某些情況下會導致鏈接時找不到符號的問題

此時可以使用

?
1
2
3
4
extern "C"
{
 ...
}

這樣在范圍內的代碼都將按照 C 的格式進行編譯

static 關鍵字在我看來的作用是

1.能夠改變變量的存儲方式

2.能夠改變變量與函數的訪問范圍

2.1 static 用于修飾變量

我們都知道當程序經過編譯后:

  • 函數體內的局部變量會保存在棧中,局部變量隨著函數的調用和返回進行構造與析構,并且在函數返回后無法使用。
  • 全局變量保存在靜態數據區直到程序退出時才會被析構掉。所以在整個程序內全局變量都可以使用(當然要考慮到作用域)。

對于局部變量,當我們在變量前加上 static 時,就是告訴了編譯器將該變量放入靜態數據區。既函數退出時不會將該變量析構掉,當我們下次再調用改函數依然可以取得內存中的這個變量。
例如 test1.cpp 中,每次調用 func1() 時 a_tmp 變量都不會被銷毀,最后輸出

0
1
2
3
4

對于全局變量,加上 static 關鍵字后該變量只能用于當前的文件。

例如 test1.cpp 中的 c_array,加上 static 后只能在當前源文件使用。

此時如果我們再在 test2.cpp 中定義一個同名的全局靜態數組進行編譯(g++ -o test test1.cpp test2.cpp)并且輸出他們的地址

?
1
2
test1.cpp[c_array]0x10bb5e100
test2.cpp[c_array]0x10bb60810

可以看到兩個地址是不同的,所以雖說是同名的兩個全局變量。但都經過 static 修飾后,他們實際還是兩個地址不同相互獨立的變量。

那么再試一下,將 test1.cpp 中的

?
1
static char c_array[10000];

修改為

?
1
extern char c_array[10000];

然后再編譯(g++ -o test test1.cpp test2.cpp)可以看到

Undefined symbols for architecture x86_64:
  "_c_array", referenced from:
      _main in test1-5d6201.o
ld: symbol(s) not found for architecture x86_64

這當然是因為 test2.cpp 中的 c_array 還是有 static 進行修飾的,導致我們無法在 test1.cpp 文件中訪問到。那就將 static 去掉,看到結果

test1.cpp[c_array]0x10b1e2100
test2.cpp[c_array]0x10b1e2100

它們的地址相同對應的同一塊內存,是同一個變量!

2.2 STATIC 用于修飾函數

static 對于函數于變量其實比較類似,它限定了函數只能在當前的模塊中使用。

假如我們將 test2.cpp 中的 func2() 函數加上 static 關鍵字,那么編譯(g++ -o test test1.cpp test2.cpp)也會報錯找不到符號

Undefined symbols for architecture x86_64:
  "func2()", referenced from:
      _main in test1-80a5c0.o
ld: symbol(s) not found for architecture x86_64

3 關于 text、bss 與 data 段

關于數據段、編譯、鏈接方面的知識非常推薦看看<<程序員的自我修養:鏈接、裝載與庫>>

3.1 局部變量的編譯

是否曾經好奇函數內的臨時變量經過編譯會變成什么樣子?

假設我們寫了如下代碼,并編譯成名為 test 的可執行文件

?
1
2
3
4
5
int main() {
 char s1[11] = "helloworld";
 char s2[11] = "helloworld";
 return 0;
}

那么可以通過 objdump -DS test觀察到 main 函數中有如下片段(有省略)

Disassembly of section .text:
.....
00000000004005b0 <main>:
4005b4: 48 b8 68 65 6c 6c 6f movabs $0x726f776f6c6c6568,%rax
4005bb: 77 6f 72
4005be: 48 89 45 f0 mov %rax,-0x10(%rbp)
4005c2: 66 c7 45 f8 6c 64 movw $0x646c,-0x8(%rbp)
4005c8: c6 45 fa 00 movb $0x0,-0x6(%rbp)
4005cc: 48 b8 68 65 6c 6c 6f movabs $0x726f776f6c6c6568,%rax
4005d3: 77 6f 72
4005d6: 48 89 45 e0 mov %rax,-0x20(%rbp)
4005da: 66 c7 45 e8 6c 64 movw $0x646c,-0x18(%rbp)
4005e0: c6 45 ea 00 movb $0x0,-0x16(%rbp)
......

觀察下 0x646c 和 0x726f776f6c6c6568,轉化成 ascii 就是

100 108 114 111 119 111 108 108 101 104

對應的字符

‘d' ‘l' ‘r' ‘o' ‘w' ‘o' ‘l' ‘l' ‘e' ‘h',看出來了吧,編譯器將 “helloworld” 以立即數的方式寫到了 text 段內。

然后通過 readelf -a test會發現并沒有 s1 與 s2 的符號。

現在將代碼改為這樣又會如何?

?
1
2
static char s1[11] = "helloworld";
static char s2[11] = "helloworld";

繼續通過 objdump -DS test觀察發現 main 中發生了改變

Disassembly of section .text:
00000000004005b0 <main>:
  4005b0: 55                    push   %rbp
  4005b1: 48 89 e5              mov    %rsp,%rbp
  4005b4: b8 00 00 00 00        mov    $0x0,%eax
  4005b9: 5d                    pop    %rbp
  4005ba: c3                    retq
  4005bb: 0f 1f 44 00 00        nopl   0x0(%rax,%rax,1)

通過 readelf -a test可以看到新增了兩個地址不同的符號,由此可見 static 確實改變了變量的存儲方式

Symbol table '.symtab' contains 66 entries:
   Num:    Value          Size Type    Bind   Vis      Ndx Name
   37: 000000000060102c    11 OBJECT  LOCAL  DEFAULT   24 _ZZ4mainE2s2
   38: 0000000000601037    11 OBJECT  LOCAL  DEFAULT   24 _ZZ4mainE2s1

那么如果指向常量呢?稍微改下

?
1
2
const char *s1 = "helloworld";
const char *s2 = "helloworld";

繼續通過 objdump 觀察到 main 中有這兩代碼,很明顯了 0x400660存儲著我們的 “helloworld”的字符串常量

00000000004005b0 <main>:
  4005b4: 48 c7 45 f8 60 06 40  movq   $0x400660,-0x8(%rbp)
  4005bb: 00
  4005bc: 48 c7 45 f0 60 06 40  movq   $0x400660,-0x10(%rbp)

找到這個地址,發現這個地址屬于 .rodata 段。這就是我們常說用來保存字面值常量的數據段。

Disassembly of section .rodata:
0000000000400658 <__dso_handle>:
 ...
  400660: 68 65 6c 6c 6f        pushq  $0x6f6c6c65
  400665: 77 6f                 ja     4006d6 <__dso_handle+0x7e>
  400667: 72 6c                 jb     4006d5 <__dso_handle+0x7d>
  400669: 64                    fs

觀察下十六進制的值,就是我們的 “helloworld” 沒錯啦。

3.2 全局變量的編譯

那么對于全局變量又應該是如何存儲的呢?

首先我們知道無論靜態還是非靜態的變量都應該存儲在靜態數據區。我們熟悉的靜態數據區就有 .bss 和 .data。
.bss 在編譯時實際上不占據空間,只有在運行時才會由被分配空間。那么還是來驗證下

?
1
2
3
4
5
char a_array[10000];
static char b_array[10000];
int main() {
 return 0;
}

編譯一下(g++ -o test test.cpp),然后通過 size 命令觀察(size test)

text    data     bss     dec     hex filename
1320     588   20048   21956    55c4 test

可以看出 a_array 和 b_array 都實際記錄在 .bss 段,并且 .data段的大小顯然不符合我們定義的數組大小。通過 ll test會發現文件大小不足10000 字節,所以可以肯定的是申請的這兩個數組在編譯時并為被分配內存。

那么繼續改一下看看

?
1
2
char a_array[10000] = "helloworld";
static char b_array[10000];

繼續使用 size test看下

text    data     bss     dec     hex filename
1320   10616   10032   21968    55d0 test

data 段和文件都多出了 10000 多字節!!!

這就是因為 a_array 進行了初始化,所以編譯器為其分配了內存。同理如果 b_array 也進行了初始化,那么大小還會增加。

tips:

如果進行了初始化,但是內存中還是 0 值的話,編譯器依舊不會為其分配內存的,例如

?
1
int a_array[10000] = {0};

總結

以上就是這篇文章的全部內容了,希望本文的內容對大家的學習或者工作具有一定的參考學習價值,如果有疑問大家可以留言交流,謝謝大家對服務器之家的支持。

原文鏈接:https://riccoqu.github.io/2019/02/23/C++%20面試之%20static%20關鍵字/

延伸 · 閱讀

精彩推薦
  • C/C++深入理解goto語句的替代實現方式分析

    深入理解goto語句的替代實現方式分析

    本篇文章是對goto語句的替代實現方式進行了詳細的分析介紹,需要的朋友參考下...

    C語言教程網7342020-12-03
  • C/C++學習C++編程的必備軟件

    學習C++編程的必備軟件

    本文給大家分享的是作者在學習使用C++進行編程的時候所用到的一些常用的軟件,這里推薦給大家...

    謝恩銘10102021-05-08
  • C/C++c++ 單線程實現同時監聽多個端口

    c++ 單線程實現同時監聽多個端口

    這篇文章主要介紹了c++ 單線程實現同時監聽多個端口的方法,幫助大家更好的理解和學習使用c++,感興趣的朋友可以了解下...

    源之緣11542021-10-27
  • C/C++詳解c語言中的 strcpy和strncpy字符串函數使用

    詳解c語言中的 strcpy和strncpy字符串函數使用

    strcpy 和strcnpy函數是字符串復制函數。接下來通過本文給大家介紹c語言中的strcpy和strncpy字符串函數使用,感興趣的朋友跟隨小編要求看看吧...

    spring-go5642021-07-02
  • C/C++C語言實現電腦關機程序

    C語言實現電腦關機程序

    這篇文章主要為大家詳細介紹了C語言實現電腦關機程序,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下...

    xiaocaidayong8482021-08-20
  • C/C++C/C++經典實例之模擬計算器示例代碼

    C/C++經典實例之模擬計算器示例代碼

    最近在看到的一個需求,本以為比較簡單,但花了不少時間,所以下面這篇文章主要給大家介紹了關于C/C++經典實例之模擬計算器的相關資料,文中通過示...

    jia150610152021-06-07
  • C/C++C++之重載 重定義與重寫用法詳解

    C++之重載 重定義與重寫用法詳解

    這篇文章主要介紹了C++之重載 重定義與重寫用法詳解,本篇文章通過簡要的案例,講解了該項技術的了解與使用,以下就是詳細內容,需要的朋友可以參考下...

    青山的青6062022-01-04
  • C/C++C語言中炫酷的文件操作實例詳解

    C語言中炫酷的文件操作實例詳解

    內存中的數據都是暫時的,當程序結束時,它們都將丟失,為了永久性的保存大量的數據,C語言提供了對文件的操作,這篇文章主要給大家介紹了關于C語言中文件...

    針眼_6702022-01-24
主站蜘蛛池模板: 国内精品久久久久久 | 一级毛片一级毛片一级毛片 | 大桥未久亚洲精品久久久强制中出 | 国产精品久久嫩一区二区免费 | 日韩精品一区二区在线观看 | 久久久精品综合 | 精品一区二区av | 中国精品自拍 | 黄色小视频在线免费观看 | 久久一区二区三区四区 | 亚洲视频在线观看网址 | av免费在线观看网站 | 午夜看片 | 99热在线播放 | 亚洲激情一区二区三区 | 蜜臀久久99精品久久久无需会员 | 精品成人佐山爱一区二区 | 国产精一区| 日韩在线免费 | 日韩精品色 | 精品久久久久久久久久 | 操操日日 | 天天插天天操 | 日韩欧美在线免费观看 | 日韩精品一区二区在线观看视频 | 亚洲综合日韩欧美 | 欧美一区二区在线视频 | 国产精品成人av | av中文在线播放 | 91视频免费网站 | 国产乱码一区二区三区 | 黄片毛片在线观看 | 性视屏| 国产精品久久久久久 | 成人3d动漫一区二区三区91 | www.日韩 | 在线午夜 | 黄色欧美视频 | 亚洲精选一区 | 亚洲影视一区 | 久久久久久91香蕉国产 |