VS 工程
一般性问题
可以这么说,你在 VS 上,配环境配半天没成,80%是 release 和 debug 搞混的问题
首先看你配的和运行的是不是同一个
然后看库是不是一致的,都是 debug 或者都是 release 最好
再看别的
值得注意的是,你当前菜单栏上的 Debug,并不意味着你打开项目的属性,会自动跳转到 Debug 上,而是停留在上次的界面,这是一个很容易失误的点。
介绍-生成
一个典型的 C++库由两部分组成
lib 和 include
include 里面是一大堆头文件
lib 里面提供了静态和动态库
静态链接,编译后,把库放在 exe 里
glfw.dll 是动态库
glfw3dll.lib 是一个静态库,与 glfw.dll 一起使用(这称之为隐式链接、导入库)
glfw.lib 静态库,静态链接时使用
项目名->属性->c++->general->附加包含目录:指的是 include 的目录
此时,单独一个文件能编译,但链接出问题
项目名->属性->linker ->general 附加库目录:指的是 vs-2019 那个 lib 目录(这块主要是指定 lib,而不是 dll)
你可能在这里引入一个 A.lib 但是它可能只是 dll 的引导库,而实际运行还是需要 A.dll
然后 linker -> 输入 附加依赖项:glfw3.lib
需要一个 dll,和是否需要导入库没关系,有时候需要导入库。
一般你用 C 的标准库,编译器会自动帮你链接到 ucrtbase.dll
动态库两类:
1、静态的 动态库版本 程序早就知道这库里有什么函数(本例)
2、加载这个库,但不知道里面有什么
动态链接:include 同时支持动静
必须同时编译 glfw.dll & glfw3dll.lib,以保证函数指针不错误
只需要修改 linker -> 输入 附加依赖项:glfw3dll.lib
运行
可以“生成”但不可以运行
1、你把 dll 拷贝到 exe 同级目录下
2、在调试里,配置“环境的值”
这里,最后面有没有分号,都可以,但是,PATH=必须写,并且写在开头
PATH=%PATH%;D:\OpenCASCADE-7.5.0-vc14-64\opencascade-7.5.0\win64\vc14\bind;D:\OpenCASCADE-7.5.0-vc14-64\tbb_2017.0.100\bin\intel64\vc14这块,不能换行,不能有空格,必须是一行之内,紧挨着写。
PATH=%PATH%;D:\OpenCASCADE-7.5.0-vc14-64\opencascade-7.5.0\win64\vc14\bind;D:\OpenCASCADE-7.5.0-vc14-64\tbb_2017.0.100\bin\intel64\vc14;D:\OpenCASCADE-7.5.0-vc14-64\ffmpeg-3.3.4-64\bin;D:\OpenCASCADE-7.5.0-vc14-64\freeimage-3.17.0-vc14-64\bin;D:\OpenCASCADE-7.5.0-vc14-64\freetype-2.5.5-vc14-64\bin;D:\OpenCASCADE-7.5.0-vc14-64\openvr-1.14.15-64\bin\win64;D:\OpenCASCADE-7.5.0-vc14-64\qt5.11.2-vc14-64\bin;D:\OpenCASCADE-7.5.0-vc14-64\tcltk-86-64\bin;D:\OpenCASCADE-7.5.0-vc14-64\vtk-6.1.0-vc14-64\bin;关于建立库
- 创建 game,点击解决方案(而不是里面的),再建立 engine,实际上在同级文件夹
- 保证 game,配置常规为应用程序,engine 改为静态库 lib
同样去 game 里,添加 c++依赖项 然后去 game,添加,引用,选择 engine 即可 就不用在 linker 里面选了
使用其它人的库
如果你使用别人的 release 的库,你有可能在 debug 模式下无法加载这个库,你只能在 release 上跑
release 也能调试
工作目录
在命令行里走到某个路径下,或者在资源管理器上双击,你可能会运行 my.exe ,这就相当于你把”工作目录“,设置为当前文件夹
如果你是在别的文件夹下,通过 D:\Learn\compiler\a.exe运行,你的”工作目录“,就在当前的文件夹,而不是 exe 所在的文件夹。
源代码里,所有的相对路径,都是相对工作目录的
而 VS,或把工作目录,自动设置为,你的源代码所在的文件夹,ProjectDir,实际上是 vcxproj 所在的位置。
这也是为什么我之前,写编译的时候,直接在源代码文件夹创建 txt,我的 exe 就能读取到,我没有进行任何设置。而到了考试机上,用 CLion 时,我必须把 txt 挪到和 exe 在同一个文件夹。
因为我的 exe 设置的是相对路径,而 CLion 把工作目录设置在了 exe 所在文件夹,自然无法读取到不在 exe 文件夹的 txt 文件。
根据以上的经验,你在配置 DLL 的时候,也需要将”工作目录“设置为 $(TargetDir) ,这样,你的代码里根据相对位置加载 dll 的代码,才能找到 DLL。
GUI 工程
在 VS 里,每个程序启动时,要选择系统
通常你写的无界面程序是 控制台
而 GUI 程序一般改为了窗口,这导致你的输出不知道跑哪去了
解决:
1、引入 windows.h,利用 OutputDebugString 输出。
2、引入 qdebug.h,利用 qDebug 输出
std::cout、printf 为什么不行呢,是因为它是 iostream,这是标准输出输出,定义的点是控制台和键盘
而控制台现在不见了(有方法可以让控制台和窗口同时在,但是这里先不谈了,对于调试来说,没必要)
另:
如果你不能调试你的程序,程序瞬间结束,你要看到你在 release 之前的输出,请打开 视图 -> 输出
把他放在底下
另:
assert 宏,定义于 assert.h 中,当程序以 release 模式运行时,一般会定义 NDEBUG,这会阻止 assert 宏的工作
另:
当你断点停不下来的时候,只能停在附近,尤其是写一个 int a =10; 这种凑数语句,停不下来,你应该检查是不是启 release 了,因为 release 能调试,但是优化的时候给优化没了。
改名
有时候,只是随手建了一个项目,随便命名
后续它逐渐变得重要起来,因此需要改名
1、改 sln 所在文件夹的名字
2、改 sln 的文件名
3、打开 sln,搜索旧的名字,替换为新的
4、给 vcxproj 三个文件改名
5、只需要打开 vcvproj,搜索旧的名字,替换为新的
那里面的 GUID 不用动。
6、如果旧的名字还包括 C 源文件的名字,也需要改
改了之后,还需要更新头文件的内容
1、删除。vs 文件夹,因为旧的数据指向的是旧的(似乎改名也行)
2、删除或移动那些可执行文件
CMake
https://cliutils.gitlab.io/modern-cmake/
由于在 windows 上,默认是生成 sln 项目
如果你要使用 mingw,要加上-G "MinGW Makefiles"
Cmake 构建文件名严格区分大小写。(linux 文件、文件夹都区分大小写的,即可以同时存在 A.txt与a.txt,而 Windows 不允许你这样)
必须叫做CMakeLists.txt三个大写字母,结尾是复数
基础:
1、单 main.c 与 Cmake 在同文件夹
cmake .
make2、PROJECT
PROJECT (HELLO C CXX) 工程的名字,支持的语言
这个关键词会额外定义两个变量,指向的是当前的工作目录
<projectname>_BINARY_DIR
<projectname>_SOURCE_DIR
3、SET
SET(SRC_LIST main.cpp t1.cpp t2.cpp)
定义变量,等价于字符串
4、ADD_EXECUTABLE
ADD_EXECUTABLE(hello $(SRC_LIST))
第一个是生成的可执行文件名,第二个是源文件
在 windows 系统下,只需要写ADD_EXECUTABLE(main $(SRC_LIST)),即可生成 main.exe
PS: 工程 PROJECT 的名字和可执行文件名字没有关系,不必一致
5、变量使用
大括号包裹(而不是小括号)
$
当一个变量中有空格,定义或使用时用引号
SET(SRC_LIST "my main file.c")
6、指令使用
指令就是指那些预定于的命令,比如 ADD_EXECUTABLE,使用时必须要括号包裹
指令名(参数 1 参数 2 ...)
指令名(参数 1; 参数 2 ...)
指令名不区分大小写,习惯上是大写。不过 CLion 默认提供的是小写的。
里面的参数严格区分大小写,尤其是文件名。
常用命令:
OPTION 条件编译、流程控制
OPTION(<变量> "提示信息" ON)
定义一个选项(或者变量),后面是 ON/OFF
通过 configure_file 就可以将这个传递给源文件使用(不清楚)
ADD_LIBRARY 构建库
ADD_LIBRARY(hello SHARED/STATIC ${MYFILES})
SHARED 表示为动态 STATIC 为静态
hello 库名,在 win 环境下,会自动构建 libhello.dll 或 libhello.lib
7、内部构建/外部构建
假设只有一个 main.c,同级文件夹下存在 CMakeLists.txt
内部构建:
运行 cmake . 后,同文件夹下出现一大堆(4 个)临时文件
CMakeFiles(文件夹) CMakeCache.txt cmake_install.cmake Makefile
请注意此时你的目标文件、可执行文件还没得到呢
运行 make 命令构建出的可执行文件,也在这个文件夹下,很乱
外部构建:
手工建立 build 文件夹,去这个文件夹下 cmake ..
就相当于把上述所有在原来文件夹下的临时文件移动到 build 里了
在 build 里 make,然后可执行文件直接放在 build 里
创建常见工程:
1、选择一个文件夹,写最外层 CMakeLists.txt,建立 src 与 build
2、最外层文件夹
PROJECT(P)
ADD_SUBDIRECTORY(src bin)ADD_SUBDIRECTORY 用于向当前工程添加存放源文件的子目录如 src,并可以指定中间二进制和目标二进制存放的位置如 bin 2、进入 src 文件,写代码,建立内侧 CMakeLists.txt
ADD_EXECUTABLE(main ${FILES})3、进入 build 文件夹,cmake .. , make
目录具体理解:
安装: 使用 CMake 进行安装操作时,你需要先构建好二进制文件。CMake 的install命令用于指定如何安装项目,但实际的安装过程是 Make 的控制下进行的。
make install 将编译好的二进制文件和其他必要的文件复制到指定的安装路径
INSTALL(FILES readme.md DESTINATION share/doc/cmake/)
#相对路径,默认位置是 /usr/local/ ,即安装到/usr/local/share/doc/cmake
#这里 share 之前为啥没斜杠?表明是相对路径。如果有斜杠,直接安装到根目录下 share 里了,蠢
#所以不要随便定义 FILES 了
INSTALL(PROGRAMS run.sh DESTINATION bin)
#安装脚本(非可执行文件),同理,bin 为相对位置,相对于/usr/local
INSTALL(DIRECTORY doc/ DESTINATION share/doc/cmake)
#这里,如果你不加斜杠,表示仅仅添加一个目录;加杠表示安装目录中内容cmake .. cd build #不是 cd build/bin 尽管这里也有 makefile make install (不执行这个,就不会开始安装)
实例:使用库
cmake_minimum_required(VERSION 3.0)
project(MyProject)
set(CMAKE_BUILD_TYPE Debug)
add_library(add STATIC add.c)
add_library(minus SHARED minus.c)
add_executable(main main.c)
target_link_libraries(main add minus)
set_target_properties(main PROPERTIES
INSTALL_RPATH "$ORIGIN/../")
#上面这句话不写也没事(假设五个文件都在最外层与 CMakeLists.txt 同级)
链接库: 注:静态链接库是直接整合到 exe 里,安装后一个。lib 都没有的 1、和 src 同级建立 lib 文件夹,建立 hello.c hello.h CMakeLists.txt
SET(LIBHELLO_SRC hello.c)
ADD_LIBRARY(hello SHARED ${LIBHELLO_SRC})2、外部同上一次的 cmake .. make
3、安装并使用(假设是动态库)
INSTALL(FILES hello.h DESTINATION include/hello)
INSTALL(TATGETS hello LIBRARY DESTINATION lib)
#默认安装到/usr/local/ 下cmake .. make make install (sudo)
4、使用安装的库 在可执行文件前,添加头文件搜索路径,源文件直接写位置即可
INCLUDE_DIRECTORYS(/usr/local/include/hello)
LINK_DIRECTORIES()
TARGET_LINK_LIBRARY(main libhello.so)
#只在环境变量里找。第一个参数是二进制可执行文件名。
#必须在 add_executable 后面(这个是什么意思呢?就是一般来讲,你要是构建动态库,不光是要构建,还要执行安装,使得可执行文件能够找到他)
Makefile
make 不局限于 C/C++,其他语言,其他非编程相关都可以使用
1、基本格式
target : prerequireds
commandtarget:可以是目标文件名,可以是可执行文件名,也可以是一个 Label 标签,表示一个“操作” 这里,由于 linux 上可执行文件没后缀名,所以,target 很可能是可执行文件的文件名
command:前面必须是一个 Tab 键,是 shell 里的命令
2、规则
make 命令会找到 Makefile 里第一个 target(不指定 target),把这个当作 target。
所以 ALL 要尽量写在前面,如果你需要第一个执行 all。一个目标可以依赖于或调用另一个目标,无论这个被依赖/调用的目标是定义在前面还是后面。make 处理目标的方式是基于它们的依赖关系,而不仅仅是它们在文件中的位置。
如果 target 文件不存在,或者它依赖的 .o 文件的修改时间比 target 新,那么就执行后面的 command 来生成 target。
如果依赖的.o 不存在,则会寻找哪个 target 生成这个.o,然后先跑那个命令。
3、伪目标
伪目标不是文件,不是文件名,只是一个 label
用 PHNOY 作为 label,表示这个目标是一个伪目标
它的作用是:让 make 认为,它后面的那个目标,不是一个文件,即不用找到那个文件,就可以运行这个命令
clean :
rm -rf dir1
debug :
echo hello
.PHNOY : clean如果没有伪目标,如果当前文件夹下存在 clean 这么一个文件,那么 make clean 就会什么都不做。而有伪目标后,执行 make clean 就不管当前文件夹下有没有 clean 文件了,只管执行命令。
debug 一般用于测试是否写对了。
所以为什么 make all。因为基本上不会出现“all”这个文件,那么你不管这么运行 make all,都会是执行命令。
进一步,你可以把 all 的依赖写成可执行文件,然后命令什么都不写。可执行文件在做为一个 target,专门写编译它的命令。这样 make 会递归的找到这个命令。
0、注释
makefile 和 Cmake 都是#开头注释 shell 文件。sh 的注释也是# 运行 ./xxx.sh (需要可执行权限) 在 shell 中输入注释:#xxxxxx
1、变量定义
= 号后不写东西,代表空值
定义的时候必须给出初值 使用的时候,需要用 $ 引用,并且变量名需要 () 或{}包裹。而 Cmake 只能是{}
你可以直接使用一个未定义的变量,视作空
2、预定义变量
$@ 在命令里使用,表示这个命令的 target 的名字 $< 这个命令的第一个依赖 $^ 这个命令的所有依赖
3、赋值运算符
= 允许一个变量多次赋值,后面的覆盖前面
:= 赋值后不在更改,即便是后面重新定义(不管用什么符号定义)
4、累加运算
+= 可以给一个用:=定义的变量赋值 就好比字符串拼接
5、换行符
\
实例:
all : main.o
@echo "find main.o"
target1 : main.c
gcc -c main.c这个例子就失败了,因为虽然依赖 main.o,但是 target 中没有说明白,make 又没办法知道 gcc 会生成 main.o
实例 2:
CC=gcc
DEBUG_FLAG=-g
all : main.c static dynamic
$(CC) $(DEBUG_FLAG) $< -o a.out -L. -lminus -ladd -Wl,-rpath,'$$ORIGIN'
static : add.c
$(CC) $(DEBUG_FLAG) -c add.c
ar -r libadd.a $(^:.c=.o)
dynamic : minus.c
$(CC) $(DEBUG_FLAG) -c -fpic minus.c
$(CC) -shared $(^:.c=.o) -o libminus.so
clean :
rm -rf *.o *.so *.a *.out
.PHONY : all clean要点 1:在命令行里使用'
chatgpt 重写的 Makefile
CC=gcc
DEBUG_FLAG=-g
STATIC_LIB=libadd.a
DYNAMIC_LIB=libminus.so
OBJECTS=add.o minus.o
all: static dynamic main
main: main.c $(STATIC_LIB) $(DYNAMIC_LIB)
$(CC) $(DEBUG_FLAG) $< -o a.out -L. -lminus -ladd -Wl,-rpath,'$$ORIGIN'
static: $(STATIC_LIB)
$(STATIC_LIB): add.o
ar -r $@ $^
dynamic: $(DYNAMIC_LIB)
$(DYNAMIC_LIB): minus.o
$(CC) -shared $^ -o $@
%.o: %.c
$(CC) $(DEBUG_FLAG) -c $< -o $@
clean:
rm -rf $(OBJECTS) $(STATIC_LIB) $(DYNAMIC_LIB) a.out
.PHONY: all static dynamic main clean技巧:把一个目标设计为文件名,之后用$@替代,而不是每个目标都要是伪目标 技巧:all 没有命令,只是给出执行顺序
实例 3: rm -rf \*~ \*.o 一些文件会以~结尾,比如 main.c~,这可能是编辑器产生的临时文件
%.o: %.c 这是一个 target,我们只能在 command 处写 rm *.o 但是 target 处不支持*,只支持%
常用变量: CC 表示 C 编译器 LD 链接器 CFLAGS 传递给 gcc 的参数 INCLUDES := -I./ -I../ -I../include/
已经执行的命令不会被回滚:Makefile 中,在错误发生之前成功执行的命令的效果将保持不变。例如,如果 Makefile 中的某个规则创建了文件或修改了文件,并且在后续的某个步骤中发生了错误,那么这些文件的创建或修改将不会自动撤销。
GCC
编译过程:四个阶段
hello.c 预处理器 cpp:比如直接把 include 插入到文件中 hello.i 编译器 ccl hello.s 汇编器 as:将上面文本的汇编转成二进制 hello.o printf.o 链接器 ld hello.out
GCC 严格的区分选项的大小写-o 与-O 完全两码事
按照步骤,一步步编译的例子: 1、源文件 -> 预处理文件
gcc -E main.c
gcc -E main.c -o helloworld.i-E 表示执行预处理,但是默认不输出文件,只输出到标准输出 -o 表示写入目标文件。此命令不管什么时候都能使用,只不过后面参数的文件名的后缀需要自己考虑。与是否生成目标文件没关系。 2、源文件 -> 汇编文件
gcc -S main.c
gcc -S main.c -o xxxxx.s前者默认生成文件,文件与源文件同名 3、源文件 -> 目标文件
gcc -c main.c
gcc -c main.c -o xxx.o默认生成与源代码同名文件 编译多个文件:
gcc -c main.c fff.x aaa.c bbb.c4、单个源文件 -> 可执行文件
gcc main.c默认是输出 a.out 。不需要其他 flag。
创建静态库文件:
假设在 add.c 中实现了一个 add 函数。在 main.c 中,先声明这个函数,然后就直接使用这个函数
1、将 add.c 编译为目标文件
gcc -c add.c minus.c2、编译为静态库
ar -r libadd.a add.o minus.o一般来说,一个静态库命名为 lib[真正库名].a
3、链接 并生成 可执行文件
gcc main.c libadd.a -o main.out创建动态库:
1、编译为目标文件(同上,先导出目标,但是有-fpic)
gcc -c -fpic xxx.c yyy.c2、编译为库
gcc -shared xxx.o yyy.o -o libadd.so3、链接到动态库
gcc main.c -o main.out -loperation -L/c/buaa/learn/src-l 指定库的名字 选项名与库名之间不允许存在空格(小写 library 的首字母)
-L 指定库路径 不允许存在空格
练习:
创建 add.a 与 minus.so
建立 add.h 和 minus.h
在 main 里引入并最后成功链接
要点:
(1)-l 后面,不需要写 lib,当你的库叫做 libadd.a 时,你只需要写-ladd
而且-l 后面也不需要指定是动态还是静态。如果都有,优先动态。
使用-static 尝试仅使用静态
(2)-L. 表示把当前路径纳入到库搜索范围。不然编译器只会在一些基本路径如/usr/bin/ld 下寻找
(3)这个时候你去执行,会发现,无法找到。so。即便是你把。so 和。out 放在一起。
因为 linux 不同于 windows,它不会在当前目录下寻找动态链接
要解决这个问题,可以尝试(仅本 shell 有效)
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:.或者将这个命令添加到脚本里,用脚本执行可执行文件
或者将动态链接库的位置写入可执行文件中
gcc -o myapp myapp.c -L. -lminus -Wl,-rpath,'$ORIGIN'-Wl,<options> 表示这个选项 gcc 不理解,将传递给 ld 连接器 -rpath,'$ORIGIN' 是连接器选项,告诉链接器将可执行文件所在的目录添加到运行时的搜索路径中。请注意,单引号是必须的,以防止 shell 展开 $ORIGIN。这个路径被编码在可执行文件中,并在运行时由动态链接器(如 ld-linux.so)使用来查找所需的动态库。
例子:-Wl,-Bdynamic -lomcgc 这个里,只有 -Bdynamic 是传递给 ld 的 options,-l 是 gcc 本来就有的,不过-l 的本质也是传给 ld
妈的:给我注意。那个-rpath 前面有一个杠,别给忘了。debug 半天,以为是别的问题。
这个例子里,你把 a.out 挪走,不行,把 a.out 和 minus.so 同时移走,可以在别的位置运行
其他选项
-g 调试模式
-O 优化级别:0 无优化 1 基本 2 进一步 3 狠狠,但是可能会增大大小? -Os=O2 且减小代码大小
-D 宏定义 相当于源代码中使用#define预处理指令。gcc -DMY_MACRO=1 ... 会导致编译器在编译过程中定义宏MY_MACRO并将其值设为1
-I include 首字母大写:添加额外的头文件搜索路径 gcc -I/my/custom/path ... 会让编译器在/my/custom/path目录下也搜索头文件
值得注意的是编译器只会在当前文件夹下搜索,而不会自动找其子文件夹(在当前文件夹下的文件又通过代码 include 到子文件夹不算),所以你会看见
$(QTDIR)\include $(QTDIR)\include\QtWidgets
补充知识: ELF(Executable and Linking Format) 这种文件同时兼具可执行、可连接的作用 .out .so 都是 ELF,这也是你为什么可以运行/lib64/ld-linux-x86-64.so.2
ldd a.out 查看依赖那些动态链接库
不运行就查看是否存在符号表: windows 系统,采用:x64 Native Tools Command Prompt for VS 2019 dumpbin /SYMBOLS "完整路径、xxx.exe"
linux 系统,objdump --syms your_executable | less
我没有使用以下语句就导出了函数,是为什么?好像是说可能是编译器默认行为。
#if defined _WIN32 || defined __CYGWIN__
/* Note: both gcc & MSVC on Windows support this syntax. */
#define FMI2_Export __declspec(dllexport)
#else
#if __GNUC__ >= 4
#define FMI2_Export __attribute__ ((visibility ("default")))
#else
#define FMI2_Export
#endifGDB
一般来说,在 vscode 里可以用远程调试,不需要在命令行里敲命令
补充:现在其实你只需要和 AI 说:“帮我把 .vscode 配置好,使得项目可以调试即可。”
编译时记得带 -g
进入程序
gdb ./main运行:键入 run 或 r (无断点就直接跑完了) 退出:quit 或 q man gdb 查看常见命令 打断点:break / b break <function-name> break mainb 10
断点设置位置:任何实际的可执行语句。不能是注释、预处理器指令或空白行
查看断点:info b 查看源代码:list (一次看不完就再 list,或者回车,回车直接执行上一次的命令)
执行下一步:n (逐过程) 注:按下 n 后,得到的标准输出是即将运行的,但还没有执行
查看变量值: p arr[0] p &arr[0]
进入执行(逐语句):step s
跳出函数:finish
运行 shell 命令:shell ls 通过前面加 shell
日志:set logging on 在 gdb.txt 里记录 gdb 的输出
观察点:watchpoint 查看是否一个变量变化,如果变了,就停下
print &i
watch *0x7fffffffde2c
info watchpoint调试一个会崩溃的程序:Segmentation fault (core dumped) gdb ./a.out core.19766
调试在运行的程序: 写一个死循环,然后。/a.out & 后台执行,返回进程号 gdb -p
补充: (1)clang 的参数和 gcc 类似,并且采用 clang 编译,也可以拿 gdb 调试 (2)GDB 中看到的可能是经过预处理后的代码,而不是原始的源代码?这个我拿自己的代码写一些条件编译,但是 gdb 还是看得到的。但是 chatgpt 说是有这种情况。