[TOC]
# 更新gcc
紅帽其實已經編譯好了高版本的gcc,但未更新到base和epel這兩個常用的源中,而是放在`scl`中。第一步便是安裝`scl`:
~~~
yum install -y centos-release-scl glibc-static
~~~
如果你之前用過`grouplist/install`等命令,應該知道gcc包含在`Development Tools`這個組中。那么`scl`中的gcc/g++軟件包的前綴都是`devtoolset`,也就不難理解了。安裝gcc 6版本的命令是:
~~~
yum install -y devtoolset-8-gcc devtoolset-8-gcc-c++
~~~
`devtoolset-6`中的gcc版本為gcc 6,除此之外還有如下版本:
* devtoolset-3: gcc 4.9
* devtoolset-4: gcc 5
* devtoolset-6: gcc 6
* devtoolset-7: gcc 7
* devtoolset-8: gcc 8
至于為什么沒有devtoolset-5,我也不清楚,估計是包含在devtoolset-4中了吧。值得說明的是這些軟件包可以同時安裝,不會相互覆蓋和沖突,也不會覆蓋系統的版本。即可以在系統中同時安裝gcc 6, gcc 7, gcc 8等多個版本。
因為不會覆蓋系統默認的gcc,使用這些軟件的方法有四種:
1. 使用絕對路徑;
2. 添加可執行文件路徑到PATH環境變量;
3. 使用官方推薦的加載命令:`scl enable devtoolset-x bash`, x為要啟用的版本;
4. 執行安裝軟件自帶的腳本: `source /opt/rh/devtoolset-x/enable`,x為要啟用的版本。
推薦使用最后兩種方式,例如啟用gcc 6: `source /opt/rh/devtoolset-6/enable`,然后輸入`gcc -v`查看版本已經變成gcc 6.3.1。使用類似的命令可以隨時在多個gcc版本中切換。如果希望長期使用高版本,可將此命令寫入`.bashrc`等配置文件。
~~~
echo "source /opt/rh/devtoolset-8/enable" >>/etc/profile
~~~
最后說一下,scl以及scl-rh源中的軟件包都安裝在/opt/rh/目錄下,包含可執行文件、配置等。所以啟用命令的路徑是`/opt/rh/xxx/enable`,安裝的服務重啟命令則可能是`systemctl restart rh-xxx`,需要加rh或scl前綴以區別其他源的包。如果你用過remi/gitlab等源,其行為方式也是類似的。
# 工作流
gcc編譯器從拿到一個c源文件到生成一個可執行程序,中間一共經歷了四個步驟:

四個步驟并不是gcc獨立完成的,而是在內部調用了其他工具,從而完成了整個工作流程:

gcc工作的流程
> deng@itcast:~/share/3rd/1gcc$ ls 1hello.c
>
> 第一步: 進行預處理
>
> deng@itcast:~/share/3rd/1gcc$ gcc -E 1hello.c -o 1hello.i
>
> 第二步: 生成匯編文件
>
> deng@itcast:~/share/3rd/1gcc$ gcc -S 1hello.i -o 1hello.s
>
> 第三步: 生成目標代碼
>
> deng@itcast:~/share/3rd/1gcc$ gcc -c 1hello.s -o 1hello.o
>
> 第四步: 生成可以執行文件
>
> deng@itcast:~/share/3rd/1gcc$ gcc 1hello.o -o 1hello 第五步: 執行 deng@itcast:~/share/3rd/1gcc?$ ./1hello hello itcast
直接將源文件生成一個可以執行文件
> deng@itcast:~/share/3rd/1gcc$ gcc 1hello.c -o 1hello deng@itcast:~/share/3rd/1gcc?$ ./1hello hello itcast
如果不指定輸出文件名字, gcc編譯器會生成一個默認的可以執行a.out
> deng@itcast:~/share/3rd/1gcc$ gcc 1hello.c
> deng@itcast:~/share/3rd/1gcc?$ ls 1hello 1hello.c 1hello.i 1hello.o 1hello.s a.out deng@itcast:~/share/3rd/1gcc?$ ./a.out
> hello itcast
# 常用選項
| **選項** | **作用** |
| --- | --- |
| \-o file | 指定生成的輸出文件名為file |
| \-E | 只進行預處理 |
| \-S(大寫) | 只進行預處理和編譯 |
| \-c(小寫) | 只進行預處理、編譯和匯編 |
| \-v / --version | 查看gcc版本號 |
| \-g | 包含調試信息 |
| \-On n=0~3 | 編譯優化,n越大優化得越多 |
| \-Wall | 提示更多警告信息 |
| \-D | 編譯時定義宏 |
顯示所有的警告信息
> gcc -Wall test.c
將警告信息當做錯誤處理
> gcc -Wall -Werror test.c
測試程序(-D選項):
~~~
#include <stdio.h>
?
int main(void)
{
printf("SIZE: %d\n", SIZE);
return 0;
}
~~~
~~~
deng@itcast:~/test$ gcc 2test.c -DSIZE=10
deng@itcast:~/test$ ./a.out
SIZE: 10
~~~
# 靜態鏈接和動態鏈接
鏈接分為兩種:**靜態鏈接**、**動態鏈接**。
**靜態、動態編譯對比**
前面我們編寫的應用程序大量用到了標準庫函數,系統默認采用動態鏈接的方式進行編譯程序,若想采用靜態編譯,加入-static參數。
以下是分別采用動態編譯、靜態編譯時文件對比:
測試程序(test.c)如下:
~~~
#include <stdio.h>
int main(void) {
printf("hello world \n");
return 0;
}
~~~
編譯:
> deng@itcast:~/test$ gcc test.c -o test\_share
>
> deng@itcast:~/test$ gcc -static test.c -o test\_static
# 靜態庫和動態庫
所謂“程序庫”,簡單說,就是包含了數據和執行碼的文件。其不能單獨執行,可以作為其它執行程序的一部分來完成某些功能。
庫的存在可以使得程序模塊化,可以加快程序的再編譯,可以實現代碼重用,可以使得程序便于升級。
程序庫可分**靜態庫(static library)**和**共享庫(shared library)**
## 靜態庫制作
靜態庫可以認為是一些目標代碼的集合,是在可執行程序運行前就已經加入到執行碼中,成為執行程序的一部分。
按照習慣,一般以“.a”做為文件后綴名。靜態庫的命名一般分為三個部分:
* 前綴:lib
* 庫名稱:自己定義即可
* 后綴:.a
所以最終的靜態庫的名字應該為:**libxxx.a**

步驟1:將c源文件生成對應的.o文件
> deng@itcast:~/test/3static\_lib$ gcc -c add.c -o add.o
> deng@itcast:~/test/3static\_lib?$ gcc -c sub.c -o sub.o
> deng@itcast:~/test/3static\_lib?$ gcc -c mul.c -o mul.o
> deng@itcast:~/test/3static\_lib?$ gcc -c div.c -o div.o
步驟2:使用打包工具ar將準備好的.o文件打包為.a文件 libtest.a
> deng@itcast: ar -rcs libtest.a add.o sub.o mul.o div.o
**在使用ar工具是時候需要添加參數:rcs**
* r更新
* c創建
* s建立索引
**靜態庫使用**
靜態庫制作完成之后,需要將.a文件和頭文件一起發布給用戶。
假設測試文件為main.c,靜態庫文件為libtest.a頭文件為head.h
編譯命令:
> deng@itcast: gcc test.c -L./ -I./ -ltest -o test
參數說明:
* \-L:表示要連接的庫所在目錄
* \-I./: I(大寫i) 表示指定頭文件的目錄為當前目錄
* \-l(小寫L):**指定鏈接時需要的庫,去掉前綴和后綴**

add.h
~~~
#ifndef __ADD_H__
#define __ADD_H__
int add(int x, int y);
#endif /* __ADD_H__ */
~~~
add.c
~~~
#include "add.h"
int add(int x, int y) {
return x + y;
}
~~~
test.c
~~~
#include <stdio.h>
#include "add.h"
int main(void) {
printf("1 + 2 = %d\n", add(1, 2));
return 0;
}
~~~
## 動態庫制作
共享庫在程序編譯時并不會被連接到目標代碼中,而是在程序運行是才被載入。不同的應用程序如果調用相同的庫,那么在內存里只需要有一份該共享庫的實例,規避了空間浪費問題。
動態庫在程序運行是才被載入,也解決了靜態庫對程序的更新、部署和發布頁會帶來麻煩。用戶只需要更新動態庫即可,增量更新。
按照習慣,一般以“.so”做為文件后綴名。共享庫的命名一般分為三個部分:
* 前綴:lib
* 庫名稱:自己定義即可
* 后綴:.so
所以最終的動態庫的名字應該為:`libxxx.so`

**1)動態庫制作**
步驟一:生成目標文件,此時要加編譯選項:-fPIC(fpic)
> deng@itcast:~/test/5share\_lib$ gcc -fPIC -c add.c
> deng@itcast:~/test/5share\_lib?$ gcc -fPIC -c sub.c
> deng@itcast:~/test/5share\_lib$ gcc -fPIC -c mul.c
> deng@itcast:~/test/5share\_lib?$ gcc -fPIC -c div.c
參數:-fPIC 創建與地址無關的編譯程序(pic,position independent code),是為了能夠在多個應用程序間共享。
步驟二:生成共享庫,此時要加鏈接器選項: -shared(指定生成動態鏈接庫)
> deng@itcast:~/test/5share\_lib$ gcc -shared add.o sub.o mul.o div.o -o libtest.so
步驟三: 通過nm命令查看對應的函數
> deng@itcast: nm libtest.so | grep add
> deng@itcast: nm libtest.so | grep sub
ldd查看可執行文件的依賴的動態庫
> deng@itcast:~/share/3rd/2share\_test$ ldd test
> linux-vdso.so.1 => (0x00007ffcf89d4000) libtest.so => /lib/libtest.so (0x00007f81b5612000) libc.so.6 => /lib/x86\_64-linux-gnu/libc.so.6 (0x00007f81b5248000) /lib64/ld-linux-x86-64.so.2 (0x00005562d0cff000)
**2)動態庫測試**
引用動態庫編譯成可執行文件(跟靜態庫方式一樣)
> deng@itcast:~/test/6share\_test$ gcc test.c -L. -I. -ltest (-I. 大寫i -ltest 小寫L)
然后運行:./a.out,發現竟然報錯了!!!

* 當系統加載可執行代碼時候,能夠知道其所依賴的庫的名字,但是還需要知道絕對路徑。此時就需要系統動態載入器(dynamic linker/loader)。
* 對于elf格式的可執行程序,是由`ld-linux.so`來完成的,它先后搜索elf文件的 DT\_RPATH段 — 環境變量LD\_LIBRARY\_PATH — /etc/ld.so.cache文件列表 —**/lib/, /usr/lib**目錄找到庫文件后將其載入內存。
**3)如何讓系統找到動態庫**
* 拷貝自己制作的共享庫到/lib或者/usr/lib(不能是/lib64目錄)
* 臨時設置LD\_LIBRARY\_PATH:
> export LD\_LIBRARY\_PATH=$LD\_LIBRARY\_PATH:庫路徑
* 永久設置,把export LD\_LIBRARY\_PATH=$LD\_LIBRARY\_PATH:庫路徑,設置到~/.bashrc或者 /etc/profile文件中
> deng@itcast:~/share/3rd/2share\_test$ vim ~/.bashrc
>
> 最后一行添加如下內容:
>
> export LD\_LIBRARY\_PATH=$LD\_LIBRARY\_PATH:/home/deng/share/3rd/2share\_test
使環境變量生效
> deng@itcast: source ~/.bashrc
> deng@itcast: ./test
> a + b = 20 a - b = 10
* 將其添加到 /etc/ld.so.conf文件中
編輯/etc/ld.so.conf文件,加入庫文件所在目錄的路徑
運行sudo ldconfig -v,該命令會重建/etc/ld.so.cache文件
> deng@itcast: sudo vim /etc/ld.so.conf
>
> 文件最后添加動態庫路徑(絕對路徑)

* > 使生效
> deng@itcast: sudo ldconfig -v
或者使用符號鏈接, 但是一定要使用絕對路徑
> deng@itcast: sudo ln -s /home/deng/test/6share\_test/libtest.so /lib/libtest.so
- c語言
- 基礎知識
- 變量和常量
- 宏定義和預處理
- 隨機數
- register變量
- errno全局變量
- 靜態變量
- 類型
- 數組
- 類型轉換
- vs中c4996錯誤
- 數據類型和長度
- 二進制數,八進制數和十六進制數
- 位域
- typedef定義類型
- 函數和編譯
- 函數調用慣例
- 函數進棧和出棧
- 函數
- 編譯
- sizeof
- main函數接收參數
- 宏函數
- 目標文件和可執行文件有什么
- 強符號和弱符號
- 什么是鏈接
- 符號
- 強引用和弱引用
- 字符串處理函數
- sscanf
- 查找子字符串
- 字符串指針
- qt
- MFC
- 指針
- 簡介
- 指針詳解
- 案例
- 指針數組
- 偏移量
- 間接賦值
- 易錯點
- 二級指針
- 結構體指針
- 字節對齊
- 函數指針
- 指針例子
- main接收用戶輸入
- 內存布局
- 內存分區
- 空間開辟和釋放
- 堆空間操作字符串
- 內存處理函數
- 內存分頁
- 內存模型
- 棧
- 棧溢出攻擊
- 內存泄露
- 大小端存儲法
- 寄存器
- 結構體
- 共用體
- 枚舉
- 文件操作
- 文件到底是什么
- 文件打開和關閉
- 文件的順序讀寫
- 文件的隨機讀寫
- 文件復制
- FILE和緩沖區
- 文件大小
- 插入,刪除,更改文件內容
- typeid
- 內部鏈接和外部鏈接
- 動態庫
- 調試器
- 調試的概念
- vs調試
- 多文件編程
- extern關鍵字
- 頭文件規范
- 標準庫以及標準頭文件
- 頭文件只包含一次
- static
- 多線程
- 簡介
- 創建線程threads.h
- 創建線程pthread
- gdb
- 簡介
- mac使用gdb
- setjump和longjump
- 零拷貝
- gc
- 調試器原理
- c++
- c++簡介
- c++對c的擴展
- ::作用域運算符
- 名字控制
- cpp對c的增強
- const
- 變量定義數組
- 盡量以const替換#define
- 引用
- 內聯函數
- 函數默認參數
- 函數占位參數
- 函數重載
- extern "C"
- 類和對象
- 類封裝
- 構造和析構
- 深淺拷貝
- explicit關鍵字
- 動態對象創建
- 靜態成員
- 對象模型
- this
- 友元
- 單例
- 繼承
- 多態
- 運算符重載
- 賦值重載
- 指針運算符(*,->)重載
- 前置和后置++
- 左移<<運算符重載
- 函數調用符重載
- 總結
- bool重載
- 模板
- 簡介
- 普通函數和模板函數調用
- 模板的局限性
- 類模板
- 復數的模板類
- 類模板作為參數
- 類模板繼承
- 類模板類內和類外實現
- 類模板和友元函數
- 類模板實現數組
- 類型轉換
- 異常
- 異常基本語法
- 異常的接口聲明
- 異常的棧解旋
- 異常的多態
- 標準異常庫
- 自定義異常
- io
- 流的概念和類庫結構
- 標準io流
- 標準輸入流
- 標準輸出流
- 文件讀寫
- STL
- 簡介
- string容器
- vector容器
- deque容器
- stack容器
- queue容器
- list容器
- set/multiset容器
- map/multimap容器
- pair對組
- 深淺拷貝問題
- 使用時機
- 常用算法
- 函數對象
- 謂詞
- 內建函數對象
- 函數對象適配器
- 空間適配器
- 常用遍歷算法
- 查找算法
- 排序算法
- 拷貝和替換算法
- 算術生成算法
- 集合算法
- gcc
- GDB
- makefile
- visualstudio
- VisualAssistX
- 各種插件
- utf8編碼
- 制作安裝項目
- 編譯模式
- 內存對齊
- 快捷鍵
- 自動補全
- 查看c++類內存布局
- FFmpeg
- ffmpeg架構
- 命令的基本格式
- 分解與復用
- 處理原始數據
- 錄屏和音
- 濾鏡
- 水印
- 音視頻的拼接與裁剪
- 視頻圖片轉換
- 直播
- ffplay
- 常見問題
- 多媒體文件處理
- ffmpeg代碼結構
- 日志系統
- 處理流數據
- linux
- 系統調用
- 常用IO函數
- 文件操作函數
- 文件描述符復制
- 目錄相關操作
- 時間相關函數
- 進程
- valgrind
- 進程通信
- 信號
- 信號產生函數
- 信號集
- 信號捕捉
- SIGCHLD信號
- 不可重入函數和可重入函數
- 進程組
- 會話
- 守護進程
- 線程
- 線程屬性
- 互斥鎖
- 讀寫鎖
- 條件變量
- 信號量
- 網絡
- 分層模型
- 協議格式
- TCP協議
- socket
- socket概念
- 網絡字節序
- ip地址轉換函數
- sockaddr數據結構
- 網絡套接字函數
- socket模型創建流程圖
- socket函數
- bind函數
- listen函數
- accept函數
- connect函數
- C/S模型-TCP
- 出錯處理封裝函數
- 多進程并發服務器
- 多線程并發服務器
- 多路I/O復用服務器
- select
- poll
- epoll
- epoll事件
- epoll例子
- epoll反應堆思想
- udp
- socket IPC(本地套接字domain)
- 其他常用函數
- libevent
- libevent簡介