【C6】编译



2022年01月08日    Author:Guofei

文章归类: C    文章编号: 10006

版权声明:本文作者是郭飞。转载随意,但需要标明原文链接,并通知本人
原文链接:https://www.guofei.site/2022/01/08/c6.html


编译

安装 gcc

sudo apt-get install gcc

gcc

gcc -o main.so main.c
# 然后执行 main 文件
./main.so

# (或者)编译+链接
gcc -o main.o main.c -c
gcc -o main main.o

# 编译优化
gcc -o main.so main.c -O3
  • O0 (默认)不进行任何优化,
    • 优点:编译消耗低(时间,空间)。debug方便,可以打断点打断,给任意的给变量赋值。
  • O1 会消耗稍多的编译时间,它主要对代码的分支,常量以及表达式等进行优化。
  • O2 会尝试更多的寄存器级的优化以及指令级的优化,它会在编译期间占用更多的内存和编译时间。
  • O3O2 的基础上进行更多的优化,例如使用伪寄存器网络,普通函数的内联,以及针对循环的更多优化。
  • Os 一般不用。主要是对代码大小的优化。会关闭一些性能优化选项。会打乱程序的结构,无法调试。会打乱执行顺序,依赖内存操作顺序的程序无法正确执行。实际基本不用这个选项。
  • Ofast 一般不用。用一些非国际标准,进一步做非常规的优化。

include

#include <stdio.h> // 表示文件在系统目录下
#include "a.h" // 表示文件在当前目录下

知识

  • main函数是执行的起点,有且只能有1个
  • printf 是向标准设备输出,未必是向屏幕输出,例如 ./main > save.txt
  • 建议用 int main,而不是 void main,后者不支持c++
  • 约定 return 0; 代表执行成功,return -1; 代表执行失败
  • 预编译:把 include 替换进来,删除注释,等操作。gcc -o main1.c main.c -E
  • 汇编 gcc -o main.s main.c -S
  • cc也可以,但gcc 多操作系统,多语言,所以一般用 gcc

多文件

// my_lib.h
// 防止重复导入
#ifndef LEARN_C_MY_LIB_H
#define LEARN_C_MY_LIB_H

int my_func1(int x);

#endif //LEARN_C_MY_LIB_H


// my_lib.c
int my_func1(x) {
    return x + 1;
}

// main.c
#include <stdio.h>
#include "my_lib.h"

int main() {
    printf("%d\n", my_func1(5));
    return 0;
}

// 编译
gcc -o main.o main.c my_lib.c
// 执行
./main.o

方式2

适合 C++ 的头文件 my_lib.h

//防止头文件被重复包含
#pragma once

//兼容C++编译器,如果遇到C++编译器,按C编译
#ifdef __cplusplus
extern "C"
{
#endif

//    这里声明你的函数
int my_func1();

#ifdef __cplusplus
}
#endif

实现文件 my_lib.c

int my_func1(){
    return 0;
}

引用 my_test.c

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

int main(void) {
    printf("%d", my_func1());
}

如何编译

gcc -o my_test my_test.c my_lib.c

方式3

不用 .h 文件

// my_lib.c
int func1(int x) {
    return x + 1;
}

// main.c
// 定义一下
int func1(int x);

int main() {
    printf("%d\n", func1(25));
}

// 编译
gcc -o main.o main.c my_lib.c
// 执行
./main.o

引入库文件

编译步骤

  1. 准备 .c/.cpp 源文件
  2. 预编译。把代码展开到 #include, #define 位置。生成 .i 文件
    • gcc -E hello.c -o hello.i
    • 或者 cpp hello.c > hello.i
  3. 编译。生成 .s 汇编语言
    • gcc -S hello.i -o hello.s
  4. 汇编。将汇编代码转变成机器可以执行的指令。生成 .o 文件(object)
    • gcc -c hello.s -o hello.o
    • 或者 as hello.s -o hello.o
  5. 链接。引用别的库。两种类型:
    • 静态库。链接后直接注入目标程序(所以文件会更大,但是链接后就不需要再保留被引用的库了)。库文件名字通常为 libxxx.a
    • 动态库。运行目标程序时加载,库文件通常以 .so 结尾
  6. 完成以上步骤后,得到最终的可执行程序

静态库

编译静态库

gcc -c hello.c -o hello.o # .c 文件编译为 .o 文件
ar rcs libhello.a hello.o # 生成静态库文件 libhello.a

关于 ar 命令

ar  archivefile  objfile
archivefile:archivefile是静态库的名称
objfile:      objfile是已.o为扩展名的中间目标文件名,可以多个并列
参数        意义
-r            将objfile文件插入静态库尾或者替换静态库中同名文件
-x            从静态库文件中抽取文件objfile
-t            打印静态库的成员文件列表
-d            从静态库中删除文件objfile
-s            重置静态库文件索引
-v            创建文件冗余信息
-c            创建静态库文件

链接静态库

gcc tst.c -o tst -L . -lhello

解读一下: -L 后面是静态库文件所在的目录,我这里 . 就是指当前目录的意思。也就是库文件就和源文件在同一路径。真正编译的时候,这个路径还是要填绝对路径要好,这个需要注意一下。后面的-l加上库名,这个库名是去掉lib和后面的.a。静态库的链接就是这样的。

动态库

使用下面的命令就可以生成一个动态库文件 libhello.so

gcc -fPIC -shared -o libhello.so hello.c
  • -fPIC 是创建与地址无关的编译程序(pic,position independent code),是为了能够在多个应用程序间共享。
  • -shared指定生成动态链接库。

调用动态库

gcc tst.c -o tst -L ./ -lhello

同样,-L后面是库文件的路径,最好是用绝对路径。-l加上去掉lib的库名。然后直接执行可执行文件就可以了。

参考文档:https://cloud.tencent.com/developer/article/1656728?from=15425

CMakeLists.txt

安装

  • 下载安装cmake:https://cmake.org/download/
  • 如果cmake 找不到: https://blog.csdn.net/songarpore/article/details/101901766
    • export PATH="/Applications/CMake.app/Contents/bin":"$PATH"
  • 或者 brew install cmake
    • 更新:brew update 更新 brew 工具,然后 brew upgrade cmake
  • linux 也可以这样安装:yum install cmake 或者 sudo apt install cmake

使用

cmake --version

cmake .
# 生成了一个 Makefile 文件

make
# 编译。结束后生成了一个可执行文件

make install
# 编译并安装到(CMakeLists.txt指定的)系统目录中

CMakeLists.txt 的内容

# 1. 基本配置
# 指定最小版本
cmake_minimum_required(VERSION 3.20)
# C99 语言
set(CMAKE_C_STANDARD 99)
# 编译优化等级为O3
set(CMAKE_C_FLAGS "-O3")

# 2. 设置项目名称
project(my_project_name C)
# 它会引入两个变量 demo_BINARY_DIR 和 demo_SOURCE_DIR,同时,cmake 自动定义了两个等价的变量 PROJECT_BINARY_DIR 和 PROJECT_SOURCE_DIR。

# 支持 C
project(my_project_name C)
# 支持 C++
project(my_project_name CXX)
# 支持 C 和 C++
project(my_project_name C CXX)


# 3. MESSAGE 输出信息
MESSAGE(STATUS "执行 cmake 。。。")
# STATUS:加入 -- 前缀
MESSAGE(SEND_ERROR "产生错误,跳过")
MESSAGE(FATAL_ERROR "产生错误,终止 cmake")

add_executable:定义如何生成可执行文件

# 定义 SRC_LIST ,可以在后面用
SET(SRC_LIST main.c
        DynamicArray/int/DynamicArray.c
        DynamicArray/int/DynamicArray.h
        )
# 可执行文件名字是 c_algorithm
add_executable(c_algorithm
        ${SRC_LIST}
        )

# 额外:变量的用法:
# ${SRC_LIST} 来使用
# 在 if 语句里面,直接用 SRC_LIST

INSTALL:在执行 make install 时的行为

add_subdirectory(src bin)

# 安装文件
INSTALL(FILES COPYRIGHT.txt README.md
        DESTINATION share/doc/cmake/)
#DESTINATION:绝对路径
#相对路径:$(CMAKE_INSTALL_PREFIX)/<路径>
#CMAKE_INSTALL_PREFIX 默认是 /usr/local/
#cmake -D CMAKE_INSTALL_PREFIX=/usr .. 可以在 cmake 时指定CMAKE_INSTALL_PREFIX的值
#因此,以上配置会安装到 /usr/local/share/doc/cmake/ 中


#安装sh文件
INSTALL(PROGRAMS runhello.sh DESTINATION bin)
#会安装到 /usr/local/bin

#安装目录
INSTALL(DIRECTORY doc/ DESTINATION share/doc/cmake)
# 用doc,会把目录安装过去
# 用 doc/ ,会把目录中的内容安装过去

# 安装可执行二进制(下面有详细)
# TARGETS

安装:意思是把相关的文件复制到系统目录中

# 构建
cd build
cmake ..
make

# build 到 ./build 文件夹中,可以防止大量临时文件污染当前目录
cmake --build ./build

# 安装
make install

构建可执行项目

  • 构建后的文件自动放入 build/bin/
  • doc/*README.mdCOPYRIGHT 放入 /usr/share/doc/cmake/

目录结构

├─src # 这个文件夹中放入源代码
│  ├─main.c
│  └─CMakeLists.txt
├─build # 在这个目录中执行 cmake ..,防止大量临时文件污染
├─doc
│  └─用来放文档.md
├─runhello.sh
├─README.md
├─LICENSE
└─CMakeLists.txt
# CMakeLists.txt
cmake_minimum_required(VERSION 3.20)
set(CMAKE_C_STANDARD 99)
set(CMAKE_C_FLAGS "-O3")
project(HELLO C)

add_subdirectory(src bin)
# 1. 把子目录 src 关联过来
# 2. 生成的可执行文件放到 bin 里面

# src/CMakeLists.txt 内容:
# (基本配置之类的同上)
add_executable(hello main.c)

构建

# 我们希望把它构建到 build 文件夹中,防止大量临时文件污染
cd build
cmake ..
# 生成了一个 Makefile 文件

make
# 编译。结束后生成了一个可执行文件

# 执行它
./bin/hello

构建动态库和静态库

动态库和静态库的区别

  • 静态库结尾是 .a, .lib。动态库结尾是 .so.dll.dylib
  • 静态库编译时直接整合到目标里面,因此编译后的目标可以独立运行。
  • 动态库编译时只是做个引用标志,编译后的目标还是需要动态库才能运行

目录结构

.
├── CMakeLists.txt
├── README.md
├── lib
│    ├── CMakeLists.txt
│    └── DynamicArray
│           ├── DynamicArray.c
│           └── DynamicArray.h
└── tests
     ├── CMakeLists.txt
     ├── main.c
     └── tests
         ├── Test_DynamicArray.c
         └── Test_DynamicArray.h

./CMakeLists.txt 这样写:

cmake_minimum_required(VERSION 3.20)
set(CMAKE_C_STANDARD 99)
set(CMAKE_C_FLAGS "-O3")

project(c_algorithm C)

add_subdirectory(lib bin)

./lib/Cmakelists.txt 这样写:

cmake_minimum_required(VERSION 3.20)
set(CMAKE_C_STANDARD 99)
set(CMAKE_C_FLAGS "-O3")

project(c_algorithm C)

include_directories(DynamicArray)
# 把子文件夹包含进来

# 把头文件放到变量 C_ALGO_H 里面,方便之后使用
set(C_ALGO_H
        DynamicArray/DynamicArray.h)

# 把代码放到变量 C_ALGO_SRC 里面,方便之后使用
set(C_ALGO_SRC
        DynamicArray/DynamicArray.c)

#构建动态库,这里生成 libc_algo.so
ADD_LIBRARY(c_algo SHARED ${C_ALGO_H} ${C_ALGO_SRC})
#c_algo 是 lib 的名字。生成的名字自动加前缀 lib,后缀 .so。
#SHARED 表示共享库。换成 STATIC 就是静态库,如下:

#构建静态库,这里生成 libhello_static.a
ADD_LIBRARY(c_algo_static STATIC ${C_ALGO_H} ${C_ALGO_SRC})
# 名字不能相同,否则报错,因此改为 c_algo_static
# 有方法可以让名字一样,略复杂:https://www.bilibili.com/video/BV1vR4y1u77h?p=8

# 安装头文件
INSTALL(FILES ${C_ALGO_H} DESTINATION include/c_algo)
# 安装动态库二进制
INSTALL(TARGETS c_algo LIBRARY DESTINATION lib)
# 安装静态库二进制
INSTALL(TARGETS c_algo_static ARCHIVE DESTINATION lib)

# 如果没有 INSTALL,也可以在子目录中生成动态文件,但实测不能用

# 如何安装:
# linux下,可以安装到系统目录 /usr,但是 MacBook 系统不允许。
# cmake -D CMAKE_INSTALL_PREFIX=/usr ..
# 否则,DESTINATION 这个变量默认就是 /usr/local,直接 cmake 即可

# cmake之后,执行下面的命令即可安装
make install

另一个项目引用它(other_project/src/CMakeLists.txt里面):

#头文件路径

include_directories(/usr/local/include/hello)
#链接动态库
target_link_libraries(main /usr/local/lib/libhello.dylib)
#或者链接静态库
#target_link_libraries(main /Users/guofei/git/my_lib/build/bin/libhello_static.a)

关于路径

  • 取决于你实际 install 的位置
  • 你可以指定任意位置,不一定非要 install

ctest

ctest 是 cmake 的一个组件

用法:

  1. 建立一个 tests 子文件夹(或者其它名字)
  2. 建立 tests/CMakeLists.txt,内容为一般的内容,要有 add_executable
  3. 外面的 CMakeLists.txt 是这样的:
add_subdirectory(tests c_algorithm_tests)
# 可执行文件放到了 c_algorithm_tests 文件夹下
enable_testing()
add_test(NAME c_algorithm_tests COMMAND c_algorithm_tests/c_algorithm_tests)
# 这里面的 `NAME c_algorithm_tests` 定义了测试名称
# `COMMAND c_algorithm_tests/c_algorithm_tests` 指定了哪个文件夹中的哪个可执行文件

编译优化

set(CMAKE_C_FLAGS "-O3")
# 类似的:
# CMAKE_C_FLAGS_DEBUG
# CMAKE_CXX_FLAGS

一些变量

# 项目路径
${PROJECT_SOURCE_DIR}

如何传参:

cmake -DSOME_VARIABLE="new value" /path/to/source
# 这个命令有些奇怪,1)传入的参数名为 SOME_VARIABLE,2)如果传参就必须写路径

# CMakeLists.txt 里面这样写:
IF (NOT DEFINED SOME_VARIABLE)
    message("没传入这个值")
ELSEIF (SOME_VARIABLE STREQUAL "some value")
    message("传入值为: ${SOME_VARIABLE}")
ELSE ()
    message("没传入some value,而是 ${SOME_VARIABLE}")
ENDIF ()

判断是否存在一个文件

if (EXISTS /usr/local/lib/libc_algo.dylib OR EXISTS /usr/local/lib/libc_algo.so OR EXISTS /usr/local/lib/libc_algo.dll)
# do something
endif ()

在某个目录中 make

make -C /path/to/directory

您的支持将鼓励我继续创作!