一些关于 CPP 的零碎知识
- 预处理阶段:
g++ -E
让 g++ 在预处理之后停止编译,主要处理头文件和宏 - 编译阶段:
g++ -S
只进行编译而不进行汇编,最终-o *.s
生成汇编代码 - 汇编阶段:
g++ -c
生成目标代码-o *.o
,也就是二进制代码 - 链接阶段:
g++ main.o test.o -o a.out
利用目标代码生成可执行文件
gcc/g++ 链接 .so/.a 文件
- .so 是 linux 的动态库,对应 windows 上的 .dll
- .a 是 linux 的静态库,对应 windows 上的 .lib
# 当前文件夹下面有库文件 libsource.a/libsource.so
g++ app.cc -L. -lsource -o app
参考:https://blog.csdn.net/FenDouZuoQingChun/article/details/103185861
CMake是一种跨平台编译工具,比make更为高级,使用起来要方便得多。CMake主要是编写CMakeLists.txt文件,然后用cmake命令将CMakeLists.txt文件转化为make所需要的makefile文件,最后用make命令编译源码生成可执行程序或共享库(so(shared object))。因此CMake的编译基本就两个步骤:cmake 和 make
实际例子
假设主目录的结构如下:
.
├── build
├── CMakeLists.txt
├── include
│ └── b.h
└── src
├── b.c
└── main.c
# 1.cmake verson,指定cmake版本
cmake_minimum_required(VERSION 3.2)
# 2.project name,指定项目的名称,一般和项目的文件夹名称对应
PROJECT(test_sqrt)
# 3.head file path,头文件目录
INCLUDE_DIRECTORIES(include)
# 4.source directory,获取 src 文件夹下面所有的.cpp/.c/.cc文件,并赋值给变量 DIR_SRCS
AUX_SOURCE_DIRECTORY(src DIR_SRCS)
# 5.set environment variable,设置环境变量,编译用到的源文件全部都要放到这里,否则编译能够通过,但是执行的时候会出现各种问题,比如"symbol lookup error xxxxx , undefined symbol"
SET(TEST_MATH ${DIR_SRCS})
# 6.add executable file,添加要编译的可执行文件
ADD_EXECUTABLE(${PROJECT_NAME} ${TEST_MATH})
# 7.add link library,添加可执行文件所需要的库,比如我们用到了libm.so(命名规则:lib+name+.so),就添加该库的名称
TARGET_LINK_LIBRARIES(${PROJECT_NAME} m)
参考:
相关命令
# 本CMakeLists.txt的project名称
# 会自动创建两个变量,PROJECT_SOURCE_DIR和PROJECT_NAME
# ${PROJECT_SOURCE_DIR}:本CMakeLists.txt所在的文件夹路径
# ${PROJECT_NAME}:本CMakeLists.txt的project名称
project(xxx)
# CMAKE_CXX_FLAGS
# https://blog.csdn.net/u013250861/article/details/127935119
# 编译选项:https://blog.csdn.net/zhizhengguan/article/details/111743586
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -D__const__=__unused__")
# 控制编译流程,相当于C语言中的宏条件编译
# https://blog.csdn.net/lhl_blog/article/details/123553686
option(<variable> "<help_text>" [value]))
# 获取路径下所有的.cpp/.c/.cc文件,并赋值给变量中
aux_source_directory(路径 变量)
# 查找对应的库
# https://www.jianshu.com/p/8a64c77343cb
# https://blog.csdn.net/comedate/article/details/109684446
find_library(<VAR> name [path1 path2 ...])
# 给文件名/路径名或其他字符串起别名,用${变量}获取变量内容
set(变量 文件名/路径/...)
# 添加编译选项
add_definitions(编译选项)
# 打印消息
message(消息)
# 编译子文件夹的CMakeLists.txt
add_subdirectory(子文件夹名称)
# 将.cpp/.c/.cc文件生成.a静态库
# 注意,库文件名称通常为libxxx.so,在这里只要写xxx即可
add_library(库文件名称 STATIC 文件)
# 将.cpp/.c/.cc文件生成可执行文件
add_executable(可执行文件名称 文件)
# 规定.h头文件路径
include_directories(路径)
# 规定.so/.a库文件路径
link_directories(路径)
# 对add_library或add_executable生成的文件进行链接操作
# 注意,库文件名称通常为libxxx.so,在这里只要写xxx即可
target_link_libraries(库文件名称/可执行文件名称 链接的库文件名称)
参考:
- https://blog.csdn.net/afei__/article/details/81201039
- CMake Reference Documentation — CMake 3.25.0-rc4 Documentation
make 命令执行时,需要一个 Makefile 文件,以告诉 make 命令需要怎么样的去编译和链接程序。
首先,我们用一个示例来说明 Makefile 的书写规则。以便给大家一个感兴认识。这个示例来源于 GNU 的 make 使用手册,在这个示例中,我们的工程有8个 C 文件,和3个头文件,我们要写一个Makefile 来告诉 make 命令如何编译和链接这几个文件。我们的规则是:
- 如果这个工程没有编译过,那么我们的所有 C 文件都要编译并被链接。
- 如果这个工程的某几个C文件被修改,那么我们只编译被修改的 C 文件,并链接目标程序。
- 如果这个工程的头文件被改变了,那么我们需要编译引用了这几个头文件的 C 文件,并链接目标程序。
目前来说一般都是编写 CMakeLists,然后 cmake 生成 Makefile,所以先掌握 cmake 相关语法,makefile 相关语法先了解
一个文件
target: prerequisites # 目标文件(可以是目标文件: .o .obj 等,也可以是执行文件,还可以是 label): 依赖项(生成 target 所需要的文件或是目标)
command # 命令(可以是任意的 shell 命令)
hello: hello.c
gcc hello.c -o hello
多个文件
bar.c包含求数组中最大值函数,foo.c包含求数组中最小值函数,各自对应的头文件是bar.h、foo.h,main_max调用求最大值函数来打印最大值,main_min调用求最小值函数打印最小值
CC = gcc
CFLAGS = -O -Wall -m64 -std=gnu89
LIBS = -lm
all: main_max main_min # 必须写成all这样的形式,否则只会生成前一个可执行文件main_max
main_max: main_max.c bar.o foo.o
$(CC) main_max.c bar.o foo.o -o main_max
main_min: main_min.c bar.o foo.o
$(CC) main_min.c bar.o foo.o -o main_min
foo.o: foo.c
$(CC) -c foo.c
bar.o: bar.c
$(CC) -c bar.c
.PHONY: clean # 为了避免与名为clean的文件冲突,最好在 clean 前面加 .PHONY
clean:
rm *.o main_max main_min
在定义好依赖关系后,后续的那一行定义了如何生成目标文件的操作系统命令,一定要以一个Tab键作为开头。记住,make 并不管命令是怎么工作的,他只管执行所定义的命令。make 会比较 targets 文件和 prerequisites 文件的修改日期,如果 prerequisites 文件的日期要比 targets 文件的日期要新,或者target不存在的话,make就会执行后续定义的命令。否则就会跳过。
参考:
C++ 可变参数:C++函数可变参数-C++函数参数三个点
C 库函数 void *memset(void *str, int c, size_t n)
复制字符 c
(一个无符号字符)到参数 str
所指向的字符串的前 n
个字符。
注意这里是按照字节赋值的,对于整形数组的赋值只能实现赋值 0 和 -1,因为它们的补码全是 0 和 1,按照字节赋值不会出错,但是如果赋值 1 或者其他值就会出错,例如:
// 1 的补码为 0000 0001
// 赋值之后某个元素为 0000 0001 0000 0001 0000 0001 0000 0001,对应十进制就是 16843009
int a[3];
memset(a, 1, sizeof a);
// 结果为:
16843009 16843009 16843009