CMake 使用

2023-05-16

1. 前言

当在做 Android NDK 开发时,如果不熟悉用 CMake 来构建,读不懂 CMakeLists.txt 的配置脚本,很容易就会踩坑,遇到编译失败,一个很小的配置问题都会浪费很多时间。所谓工欲善其事必先利其器,学习 NDK 开发还是要大致了解 CMake 的基本语法和配置的。

2. CMake 是什么?

CMake 是一个开源的跨平台自动化构建系统。官网地址:CMake

2.1CMake 的特点

  • 1)开放源代码,使用类 BSD 许可发布。
  • 2)跨平台,并可生成 native 编译配置文件,在 Linux/Unix 平台,生成 makefile,在
    Mac 平台,可以生成 xcode,在 Windows 平台,可以生成 MSVC 的工程文件。
  • 3)能够管理大型项目;
  • 4)简化编译构建过程和编译过程。Cmake 的工具链非常简单:cmake+make。
  • 5)高效率;
  • 6)可扩展,可以为 cmake 编写特定功能的模块,扩充 cmake 功能。

2.2 使用建议

1)如果你没有实际的项目需求,那么看到这里就可以停下来了,因为 CMake 的学习过程就是实践过程,没有实践,读的再多几天后也会忘记;
2)如果你的工程只有几个文件,直接编写 Makefile 是最好的选择;(那得学习 make 命令和熟悉 Makefile 的构建规则,这是另外一回事了)
3)如果使用的是 C/C++/Java 之外的语言,请不要使用 CMake;
4)如果你使用的语言有非常完备的构建体系,比如 java 的 ant,也不需要学习 cmake;
5)如果项目已经采用了非常完备的工程管理工具,并且不存在维护问题,没有必要迁移到CMake

CMakeLists.txt 文件是 CMake 的构建定义文件。如果工程存在多个目录,需要在每个要管理的目录都添加一个 CMakeLists.txt 文件。

3. CMake 命令

CMake 命令行格式有很多种,这里只介绍一种比较常用的

cmake [<options>] (<path-to-source> | <path-to-existing-build>)   

options 为可选项,为空时,构建的路径为当前路径。
options 的值,可以通过输入cmake --help 或到官方文档CMake-cmake查看,比如:
-G <generator-name> 是指定构建系统的生成器,当前平台所支持的 generator-name 也可以通过cmake --help查看。(options 一般默认为空就好,这里不做过多介绍)

path-to-sourcepath-to-existing-build二选一,分别表示 CMakeLists.txt 所在的路径和一个已存在的构建工程的目录

  • cmake .表示构建当前目录下 CMakeLists.txt 的配置,并在当前目录下生成 Makefile 等文件;【属于内部构建】
  • cmake ..表示构建上一级目录下 CMakeLists.txt 的配置,并在当前目录下生成 Makefile 等文件;
  • cmake [参数] [指定进行编译的目录或存放Makefile文件的目录] [指定CMakeLists.txt文件所在的目录] 【属于外部构建】

附:内部构建(in-source build)与外部构建(out-of-source build)
内部构建生成的临时文件可能比源代码还要多,非常影响工程的目录结构和可读性。而CMake 官方建议使用外部构建,外部构建可以达到将生成中间产物与源代码分离。

4. Hello World CMake

注:以下 Mac 平台

安装 CMake (Windows 可以到官网下载安装包安装 Download | CMake)

brew install cmake
brew link cmake
cmake -version #检验是否安装成功,显示对应 CMake 版本号即表示安装成功

创建一个 CMake/t1 目录,并分别编写 main.c 和 CMakeLists.txt (CMakeLists.txt 是 CMake 的构建定义文件)

#include <stdio.h>
int main()
{
    printf(“Hello World from CMake!\n”);
    return 0;
}

PROJECT(HELLO)
SET(SRC_LIST main.c)
MESSAGE(STATUS "This is BINARY dir " ${HELLO_BINARY_DIR})  #终端打印的信息
MESSAGE(STATUS "This is SOURCE dir "${HELLO_SOURCE_DIR})
ADD_EXECUTABLE(hello ${SRC_LIST})

这里如果直接输入cmake .开始构建,属于内部构建。建议采用外部构建的方法,先建一个 build 文件夹,进入 build 文件夹在执行cmake ..。构建后出现很多 log 包含以下,说明构建成功,并且目录下会生成CMakeFiles, CMakeCache.txt, cmake_install.cmake, Makefile 等文件

-- This is BINARY dir /Users/cfanr/AndroidStudioProjects/NDK/CMake/t1
-- This is SOURCE dir /Users/cfanr/AndroidStudioProjects/NDK/CMake/t1
-- Configuring done
-- Generating done
-- Build files have been written to: /Users/cfanr/AndroidStudioProjects/NDK/CMake/t1

然后在执行 make命令,会生成 main.c 对应的可执行文件hello,并会出现以下彩色的 log

[ 50%] Building C object CMakeFiles/hello.dir/main.c.o
[100%] Linking C executable hello
[100%] Built target hello

最后执行 ./hello 会打印输出:
Hello World from CMake!

5. CMake 的基本语法规则

  • 使用星号 # 作为注释;
  • 变量使用 ${} 方式取值,但是在 IF 控制语句中是直接使用变量名;
  • 指令名(参数1 参数2 …),其中参数之间使用空格或分号隔开;
  • 指令与大小写无关,但参数和变量是大小写相关的;

6. CMake 的常用指令

注:指令与大小写无关,官方建议使用大写,不过 Android 的 CMake 指令是小写的,下面为了便于阅读,采取小写的方式。

6.1 project 指令

语法:project(<projectname> [CXX] [C] [Java])
这个指令是定义工程名称的,并且可以指定工程支持的语言(当然也可以忽略,默认情况表示支持所有语言),不是强制定义的。例如:project(HELLO)
定义完这个指令会隐式定义了两个变量:
<projectname>_BINARY_DIR<projectname>_SOURCE_DIR
由上面的例子也可以看到,MESSAGE 指令有用到这两个变量;

另外 CMake 系统还会预定义了 PROJECT_BINARY_DIRPROJECT_SOURCE_DIR 变量,它们的值和上面两个的变量对应的值是一致的。不过为了统一起见,建议直接使用PROJECT_BINARY_DIRPROJECT_SOURCE_DIR,即使以后修改了工程名字,也不会影响两个变量的使用。

6.2 set 指令

语法:set(VAR [VALUE])
这个指令是用来显式地定义变量,多个变量用空格或分号隔开
例如:set(SRC_LIST main.c test.c)

注意,当需要用到定义的 SRC_LIST 变量时,需要用${var}的形式来引用,如:${SRC_LIST}
不过,在 IF 控制语句中可以直接使用变量名。

6.3 message 指令

语法:message([SEND_ERROR | STATUS | FATAL_ERROR] “message to display” … )
这个指令用于向终端输出用户定义的信息,包含了三种类型:
SEND_ERROR,产生错误,生成过程被跳过;
STATUS,输出前缀为—-的信息;(由上面例子也可以看到会在终端输出相关信息)
FATAL_ERROR,立即终止所有 CMake 过程;

6.4 add_executable 指令

语法:add_executable(executable_file_name [source])
将一组源文件 source 生成一个可执行文件。 source 可以是多个源文件,也可以是对应定义的变量
如:add_executable(hello main.c)

6.5 cmake_minimun_required(VERSION 3.4.1)

用来指定 CMake 最低版本为3.4.1,如果没指定,执行 cmake 命令时可能会出错

6.6 add_subdirectory 指令

语法:add_subdirectory(source_dir [binary_dir] [EXCLUDE_FROM_ALL])
这个指令用于向当前工程添加存放源文件的子目录,并可以指定中间二进制和目标二进制存放的位置。EXCLUDE_FROM_ALL参数含义是将这个目录从编译过程中排除。

另外,也可以通过 SET 指令重新定义 EXECUTABLE_OUTPUT_PATH 和 LIBRARY_OUTPUT_PATH 变量来指定最终的目标二进制的位置(指最终生成的 hello 或者最终的共享库,不包含编译生成的中间文件)
set(EXECUTABLE_OUTPUT_PATH ${PROJECT_BINARY_DIR}/bin)
set(LIBRARY_OUTPUT_PATH ${PROJECT_BINARY_DIR}/lib)

6.7 add_library 指令

语法:add_library(libname [SHARED | STATIC | MODULE] [EXCLUDE_FROM_ALL] [source])
将一组源文件 source 编译出一个库文件,并保存为 libname.so (lib 前缀是生成文件时 CMake自动添加上去的)。其中有三种库文件类型,不写的话,默认为 STATIC:

  • SHARED: 表示动态库,可以在(Java)代码中使用 System.loadLibrary(name) 动态调用;
  • STATIC: 表示静态库,集成到代码中会在编译时调用;
  • MODULE: 只有在使用 dyId 的系统有效,如果不支持 dyId,则被当作 SHARED 对待;
  • EXCLUDE_FROM_ALL: 表示这个库不被默认构建,除非其他组件依赖或手工构建

#将compress.c 编译成 libcompress.so 的共享库
add_library(compress SHARED compress.c)

add_library 命令也可以用来导入第三方的库:
add_library(libname [SHARED | STATIC | MODULE | UNKNOWN] IMPORTED)
如,导入 libjpeg.so

add_library(libjpeg SHARED IMPORTED)

导入库后,当需要使用 target_link_libraries 链接库时,可以直接使用该库

6.8 find_library 指令

语法:find_library(<VAR> name1 path1 path2 ...)
VAR 变量表示找到的库全路径,包含库文件名 。例如:

find_library(libX  X11 /usr/lib)
find_library(log-lib log)  #路径为空,应该是查找系统环境变量路径

6.9 set_target_properties 指令

语法: set_target_properties(target1 target2 … PROPERTIES prop1 value1 prop2 value2 …)
这条指令可以用来设置输出的名称(设置构建同名的动态库和静态库,或者指定要导入的库文件的路径),对于动态库,还可以用来指定动态库版本和 API 版本。
如,set_target_properties(hello_static PROPERTIES OUTPUT_NAME “hello”)
设置同名的 hello 动态库和静态库:

set_target_properties(hello PROPERTIES CLEAN_DIRECT_OUTPUT 1)
set_target_properties(hello_static PROPERTIES CLEAN_DIRECT_OUTPUT 1)

指定要导入的库文件的路径

add_library(jpeg SHARED IMPORTED)
#注意要先 add_library,再 set_target_properties
set_target_properties(jpeg PROPERTIES IMPORTED_LOCATION ${PROJECT_SOURCE_DIR}/libs/${ANDROID_ABI}/libjpeg.so)

设置动态库 hello 版本和 API 版本:
set_target_properties(hello PROPERTIES VERSION 1.2 SOVERSION 1)

和它对应的指令:
get_target_property(VAR target property)
如上面的例子,获取输出的库的名字

get_target_property(OUTPUT_VALUE hello_static OUTPUT_NAME)
message(STATUS "this is the hello_static OUTPUT_NAME:"${OUTPUT_VALUE})

6.10 include_directories 指令

语法:include_directories([AFTER | BEFORE] [SYSTEM] dir1 dir2…)
这个指令可以用来向工程添加多个特定的头文件搜索路径,路径之间用空格分割,如果路径中包含了空格,可以使用双引号将它括起来,默认的行为是追加到当前的头文件搜索路径的
后面。

6.11 target_link_libraries 指令

语法:target_link_libraries(target library <debug | optimized> library2…)
这个指令可以用来为 target 添加需要的链接的共享库,同样也可以用于为自己编写的共享库添加共享库链接。
如:

#指定 compress 工程需要用到 libjpeg 库和 log 库
target_link_libraries(compress libjpeg ${log-lib})

同样,link_directories(directory1 directory2 …) 可以添加非标准的共享库搜索路径。

还有其他 file、list、install 、find_ 指令和控制指令等就不介绍了,详细可以查看手册。

7. CMake 的常用变量

7.1 变量引用方式

使用 ${} 进行变量的引用。不过在 IF 等语句中,可以直接使用变量名而不用通过 ${} 取值

7.2 自定义变量的方式

主要有隐式定义和显式定义两种。隐式定义,如 PROJECT 指令会隐式定义<projectname>_BINARY_DIR 和 <projectname>_SOURCE_DIR
而对于显式定义就是通过 SET 指令来定义。如:set(HELLO_SRC main.c)

7.3 CMake 常用变量

  • 1)CMAKE_BINARY_DIR, PROJECT_BINARY_DIR, <projectname>_BINARY_DIR
    这三个变量指代的内容都是一样的,如果是 in-source 编译,指的是工程顶层目录,如果是 out-of-source 编译,指的是工程编译发生的目录。

  • 2)CMAKE_SOURCE_DIR, PROJECT_SOURCE_DIR, <projectname>_SOURCE_DIR
    这三个变量指代的内容也是一样的,不论哪种编译方式,都是工程顶层目录。

  • 3)CMAKE_CURRENT_SOURCE_DIR
    当前处理的 CMakeLists.txt 所在的路径

  • 4)CMAKE_CURRENT_BINARY_DIR
    如果是 in-source 编译,它跟 CMAKE_CURRENT_SOURCE_DIR 一致,如果是 out-of-source 编译,指的是 target 编译目录。
    使用 ADD_SUBDIRECTORY(src bin)可以修改这个变量的值;
    而使用 SET(EXECUTABLE_OUTPUT_PATH < 新路径>) 并不会对这个变量造成影响,它仅仅修改了最终目标文件存放的路径。

  • 5)CMAKE_CURRENT_LIST_FILE
    输出调用这个变量的 CMakeLists.txt 的完整路径

  • 6)CMAKE_CURRENT_LIST_LINE
    输出这个变量所在的行

  • 7)CMAKE_MODULE_PATH
    这个变量用来定义自己的 CMake 模块所在的路径。如果你的工程比较复杂,有可能会自己
    编写一些 cmake 模块,这些 cmake 模块是随你的工程发布的,为了让 cmake 在处理
    CMakeLists.txt 时找到这些模块,你需要通过 SET 指令,将自己的 cmake 模块路径设
    置一下。
    比如 SET(CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake)
    这时候你就可以通过 INCLUDE 指令来调用自己的模块了。

  • 8)EXECUTABLE_OUTPUT_PATH 和 LIBRARY_OUTPUT_PATH
    分别用来重新定义最终结果的存放目录,前面我们已经提到了这两个变量。

  • 9)PROJECT_NAME
    返回通过 PROJECT 指令定义的项目名称。

介绍了那么多,可以通过一些小练习来巩固下,参考:cmake 学习笔记(一) - dbzhang800- CSDN博客

代码地址:NdkSample/CMake Sample

8. Android CMake 的使用

8.1 CMakeList.txt 的编写

再回归到 Android NDK 开发中 CMake 的使用,先看一个系统生成的 NDK 项目的 CMakeLists.txt 的配置:( 去掉原有的注释)

#设置编译 native library 需要最小的 cmake 版本
cmake_minimum_required(VERSION 3.4.1)
#将指定的源文件编译为名为 libnative-lib.so 的动态库
add_library(native-lib SHARED src/main/cpp/native-lib.cpp)
#查找本地 log 库
find_library(log-lib log)
#将预构建的库添加到自己的原生库
target_link_libraries(native-lib ${log-lib} )

复杂一点的 CMakeLists,这是一个本地使用 libjpeg.so 来做图片压缩的项目

cmake_minimum_required(VERSION 3.4.1)

#设置生成的so动态库最后输出的路径
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${PROJECT_SOURCE_DIR}/src/main/jniLibs/${ANDROID_ABI})

#指定要引用的libjpeg.so的头文件目录
set(LIBJPEG_INCLUDE_DIR src/main/cpp/include)
include_directories(${LIBJPEG_INCLUDE_DIR})

#导入libjpeg动态库 SHARED;静态库为STATIC
add_library(jpeg SHARED IMPORTED)
#对应so目录,注意要先 add_library,再 set_target_properties)
set_target_properties(jpeg PROPERTIES IMPORTED_LOCATION ${PROJECT_SOURCE_DIR}/libs/${ANDROID_ABI}/libjpeg.so)

add_library(compress SHARED src/main/cpp/compress.c)

find_library(graphics jnigraphics)
find_library(log-lib log)
#添加链接上面个所 find 和 add 的 library
target_link_libraries(compress jpeg ${log-lib} ${graphics})

8.2 配置 Gradle

简单的配置如下,至于 cppFlags 或 cFlags 的参数有点复杂,一般设置为空或不设置也是可以的,这里就不过多介绍了

android {
    compileSdkVersion 25
    buildToolsVersion "25.0.3"

    defaultConfig {
        minSdkVersion 15
        targetSdkVersion 25
        versionCode 1
        versionName "1.0"

        externalNativeBuild {
            cmake {
                // Passes optional arguments to CMake.
                arguments "-DANDROID_ARM_NEON=TRUE", "-DANDROID_TOOLCHAIN=clang"
                // Sets optional flags for the C compiler.
                cFlags "-D_EXAMPLE_C_FLAG1", "-D_EXAMPLE_C_FLAG2"
                // Sets a flag to enable format macro constants for the C++ compiler.
                cppFlags "-D__STDC_FORMAT_MACROS"
                //生成.so库的目标平台
                abiFilters 'x86', 'x86_64', 'armeabi', 'armeabi-v7a',
                   'arm64-v8a'
            }
        }
    }
      //配置 CMakeLists.txt 路径
    externalNativeBuild {
        cmake {
            path "CMakeLists.txt"
        }
    }
}

对于 CMake 的知识点其实还是有很多的,这里只是简单介绍了 CMake 的基本语法规则和使用方法,了解了这些,遇到问题应该也能快速定位到原因,找出解决的版本,就算不记得一些指令,也通过查找文档解决。能达到这种程度,对于 Android NDK 开发来说,掌握这些也足够了吧。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

CMake 使用 的相关文章

  • cmake 找不到 Qt4

    由于4 8 0已经发布 我重新安装了Qt 现在我也想使用cmake 为了使 cmake 工作 我记得必须添加 mingw bin 文件夹 QtSDK Desktop Qt 4 7 3 到Qt4 7 3中的PATH 所以我猜测在中会有一个类似
  • 在 Ubuntu 16.04 上编译 PCL 1.7,CMake 生成的 Makefile 中出现错误

    我正在尝试让 PCL 1 7 点云库 而不是其他 pcl 在 Ubuntu 16 04 上运行 我最终希望用于 C 的东西 但现在我只是想让这些例子工作 我使用的是 Ubuntu GNU 5 3 1 附带的默认编译器和 Cmake 版本 3
  • 如何使我的单元测试适应 cmake 和 ctest?

    到目前为止 我已经使用了一个临时的单元测试程序 基本上是由批处理文件自动运行的全部单元测试程序 尽管其中很多都明确地检查了他们的结果 但还有更多的作弊行为 他们将结果转储到版本控制的文本文件中 测试结果中的任何更改都会被颠覆标记 我可以轻松
  • 如何判断给定目标是库还是可执行文件?

    内置功能install TARGETS 将库目标安装到可执行目标之外的其他位置 我想做类似的事情 给定目标名称列表 我想将其中的所有库目标添加到列表变量中 并将所有运行时目标添加到另一个变量中 我找不到 CMake 的默认目标属性列表 但我
  • 在cmake中集成bash测试脚本

    我有一个 C cmake 项目 它使用 Boost Test 进行单元测试 现在我想向 cmake 公开一系列 bash 脚本 用于集成测试 假设每个脚本在以下情况下返回 0PASS或某些情况下 0FAILURE 我希望每次运行时都执行每个
  • 将 Doctest 与代码一起使用时将实现放在哪里

    我在用着doctest https github com onqtam doctest用于我的 C 项目中的测试 我想将测试代码与我的实现放在一起 正如库所说是可能的 但我似乎不知道如何处理 doctest 实现代码 我有一个doctest
  • Clang 与 CLion:无法获取编译器信息

    我尝试通过更改在 CLion 中从 gcc 切换到 clang工具链偏爱 但现在 cmake 失败并显示以下内容 Cannot get compiler information Compiler exited with error code
  • Cygwin 下使用 CMake 编译库

    我一直在尝试使用 CMake 来编译 TinyXML 作为一种迷你项目 尝试学习 CMake 作为补充 我试图将其编译成动态库并自行安装 以便它可以工作 到目前为止 我已经设法编译和安装它 但它编译成 dll 和 dll a 让它工作的唯一
  • 动态加载库和共享全局符号

    由于我在动态加载的库中观察到全局变量的一些奇怪行为 因此我编写了以下测试 首先我们需要一个静态链接库 头文件test hpp ifndef BASE HPP define BASE HPP include
  • 使用 CMake 在 iOS 中使用另一个 STATIC 库创建一个 STATIC 库

    我有一个 libfooi a 的集合 libfoo1 a libfoo2 a libfoo3 a 使用工厂 带有静态代码 有一个公共接口来创建 C 对象 使用 CMake 我选择其中之一 并创建一个链接它并添加所有内容的 libfooWra
  • 如何在Linux上构建GLFW3项目?

    我已经使用 cmake 和 make 编译了 glfw3 和包含的示例 没有出现任何问题 开始编写我的第一个项目 作为 opengl 和 glfw 的新手 并且对 C 和 CMake 没有经验 我正在努力理解示例构建文件 甚至要链接哪些库和
  • Cmake选项默认值

    如果我有一个 CMakeLists txt 文件 cmake minimum required VERSION 2 8 OPTION FOO Foo Option OFF MESSAGE FOO FOO 然后我调用 cmake 得到以下输出
  • CMake(Ninja 后端)使用 /MT 编译

    我有一个类似的问题CMake 使用 MT 而不是 MD 进行编译 https stackoverflow com questions 14172856 cmake compile with mt instead of md但有一些差异 我正
  • 在 CMake 中使用 GLOB 指定源文件还是单独指定每个文件更好?

    CMake 提供了多种方法来指定目标的源文件 一种是使用通配符 文档 https cmake org cmake help latest command file html 例如 FILE GLOB MY SRCS dir 另一种方法是单独
  • 如何指定CMAKE外部项目的编译器?

    我使用ExternalProject Add 包含一个外部项目 我想要的是能够做到 cmake DCMAKE CXX COMPILER
  • 如何在cmake中将构建类型更改为Release模式?

    我正在尝试以发布模式构建一个项目 默认情况下它是在调试模式下构建的 我正在设置变量CMAKE BUILD TYPE到 释放 CMakeLists txt 但它仍在调试模式下构建项目 当我在 CMake 命令中传递 Release 作为构建类
  • Opencv - 找不到头文件

    我正在尝试使用 opencv 开始开发 问题是 到目前为止我几乎无法设置 opencv 因为我找不到它的头文件 我对此主题进行了一些研究 但没有一个真正有帮助 下面是一些链接 opencv2 包含文件在哪里 https stackoverf
  • 如何在 CMake 中运行基本的“add_custom_command”

    我只是想启动并运行一个基本的 CMake 示例 它可以运行一些基本的命令行命令 我已经研究这个有一段时间了 但没有任何运气 我完全用错了吗 任何和所有的意见将不胜感激 cmake minimum required VERSION 3 0 a
  • 如何使用cmake自动构建第三方库

    我在寻找什么 下载库 提取它 应用自定义补丁 运行配置 运行构建命令 我正在尝试构建的库是 Openssl Boost Thrift C ares Curl Pcre Nginx ICU JsonCPP 我想我可以使用外部模块做这些事情 h
  • 如何配置 cmake 以链接到预构建的共享库?

    我有一个项目 在子目录中包含 opencv 的预构建版本 例如 我的项目 CMakeLists txt src 第三者 CMakeLists txt 开放式CV 包括 库 我想链接位于third party目录中的opencv版本 我的问题

随机推荐

  • java消息队列,业务应用场景概述

    1 异步处理 场景说明 xff1a 用户注册后 xff0c 需要发注册邮件和注册短信 传统的做法有两种 1 串行的方式 xff1b 2 并行方式 a 串行方式 xff1a 将注册信息写入数据库成功后 xff0c 发送注册邮件 xff0c 再
  • TCP三次握手,四次挥手

    三次握手 xff1a 第一次握手 SYN 61 1 seq 61 x xff0c 发送完毕后 xff0c 客户端进入 SYN SEND 状态 第二次握手 SYN 61 1 ACK 61 1 seq 61 y ACKnum 61 x 43 1
  • 206. 反转链表

    方法一 xff1a 迭代 假设链表为 1 2 3 xff0c 我们想要把它改成 1 2 3 在遍历链表时 xff0c 将当前节点的 next 指针改为指向前一个节点 由于节点没有引用其前一个节点 xff0c 因此必须事先存储其前一个节点 在
  • 启动不了docker怎么办?关于docker报错

    常常有时候电脑重启之后或者前一天正常关机第二天就启动不了的问题 xff1f 问题描述 从terminal启动ubuntu1804报错 参考的对象类型不支持尝试的操作 直接启动ubuntu1804也不行 解决方法 以左下角鼠标右键管理员身份打
  • Java 并发编程(一)

    多线程带来的风险 public class Unsafe private int chenmo public int add return chenmo 43 43 上面这段代码在单线程的环境中可以正确执行 xff0c 但在多线程的环境中则
  • Java并发编程(二):保证共享变量的原子性

    怎么让一个类在多线程的环境下是安全的 xff0c 有 3 条法则 xff0c 让我来告诉你 xff1a 1 不在线程之间共享状态变量 2 将状态变量改为不可变 3 访问状态变量时使用同步 那你可能要问 xff0c 状态变量是什么 xff1f
  • Java 并发编程(三):保证共享变量的可见性

    Java 内存模型 xff08 Java Memory Model xff0c 简称 JMM xff09 描述了 Java 程序中各种变量 xff08 线程之间的共享变量 xff09 的访问规则 xff0c 以及在 JVM 中将变量存储到内
  • Java 并发编程(四):保证对象的线程安全性

    02 线程安全类 设计一个线程安全类需要三个步骤 xff1a 1 xff09 找出表示对象状态的所有变量 2 xff09 对变量进行有效性约束 3 xff09 增加类的并发访问策略 我在作者说的基础上做了微调 xff0c 读起来更加容易理解
  • gradle依赖变量

    settings gradle对象生成早于app build gradle甚至早于根目录的build gradle 所以在build gradle里声明ext someVar 61 xxx 变量无效 settings无法访问 因此在grad
  • AudioTrack分析

    第一部分 AudioTrack分析 一 目的 本文的目的是通过从Audio系统来分析Android的代码 xff0c 包括Android自定义的那套机制和一些常见类的使用 xff0c 比如Thread xff0c MemoryBase等 分
  • git 报错信息:SSL certificate problem: certificate has expired 解决方案

    执行命令 git config global http sslVerify false 再次执行 git pull 成功拉取
  • 获取本机号码及sim卡信息

    一 SIM卡存储的数据可分为四类 xff0c 它们分别是 xff1a 第一类是固定存放的数据 这类数据在移动电话机被出售之前由SIM卡中心写入 xff0c 包括国际移动用户识别号 xff08 IMSI xff09 鉴权密钥 xff08 KI
  • Socket粘包问题的3种解决方案

    在 Java 语言中 xff0c 传统的 Socket 编程分为两种实现方式 xff0c 这两种实现方式也对应着两种不同的传输层协议 xff1a TCP 协议和 UDP 协议 xff0c 但作为互联网中最常用的传输层协议 TCP xff0c
  • Android aar包的使用 打包aar后报错ClassNotFoundException,aar中有dependencies依赖的情况;

    示例 xff1a 现有A xff0c B两个library module都是本地的 xff0c A引用B xff0c 我把A打成了aar库 xff0c 打出来之后却没有B的内容 xff0c 导致我的主工程引用A之后 xff0c 执行某个应用
  • MFC架构下的DirectX8

    MFC架构下的DirectX8 第一章 MFC框架 DX8MFC 这里的MFC框架指的是一个符合游戏开发应用的框架 xff0c 当然你也可以写一个符合你要求的MFC框架 如果你对MFC比较熟悉的话可以直接从第二章开始阅读 本框架是以后几个例
  • 解决:There is no tracking information for the current branch. Please specify which branch you want to

    报警信息 xff1a There is no tracking information for the current branch Please specify which branch you want to rebase agains
  • git删除远程分支和本地分支

    1 xff09 使用命令git branch a 查看所有分支 注 xff1a 其中 xff0c remote origin master表示的是远程分支 xff08 2 xff09 删除远程分支 注 xff1a 如上所示 xff0c 使用
  • Java 集合 List 与 Array 的转换

    List 转 Array 使用集合转数组的方法 xff0c 必须使用集合的 toArray T array xff0c 传入的是类型完全一样的数组 xff0c 大小就是 list size 反例 xff1a 直接使用 toArray 无参方
  • C语言中.h和.c文件解析

    简单的说其实要理解C文件与头文件 xff08 即 h xff09 有什么不同之处 xff0c 首先需要弄明白编译器的工作过程 xff0c 一般说来编译器会做以下几个过程 xff1a 1 预处理阶段 2 词法与语法分析阶段 3 编译阶段 xf
  • CMake 使用

    1 前言 当在做 Android NDK 开发时 xff0c 如果不熟悉用 CMake 来构建 xff0c 读不懂 CMakeLists txt 的配置脚本 xff0c 很容易就会踩坑 xff0c 遇到编译失败 xff0c 一个很小的配置问