C/C++ 模板元编程学习

模板 在C++中使用模板(templates)来进行泛型编程,它允许程序员编写与数据类型无关的代码,模板可以是函数模板或类模板。 模板形式 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 #include <iostream> // 函数模板 template <class T, int i = 76> T func(T t) { std::cout << i << std::endl; return t * 2; } // 类模板 template <class T> class MyClass { public: static void func(T t) { std::cout << t << std::endl; } }; int main() { // 调用函数模板 std::cout << func<int>(1) << std::endl; std::cout << func(1.1) << std::endl; // 调用类模板 MyClass<int>::func(10); return 0; } 函数模板可以自动推导参数类型,如上例中的func(1.1)。 模板参数也可以有默认值,此时调用者在调用时没有指定时,会使用默认值。 在函数模板中,为了一些特殊情况,可以添加一个同名函数,它可以与函数模板重载: 1 2 3 4 5 6 7 template <class T> T twice(T t) { return t * 2; } std::string twice(std::string t) { return t + t; } 整数也可以作为模板参数,比如template <int N>,但模板参数只支持整数类型(包括enum),浮点类型、指针类型,不能声明为模板参数 整数作为函数参数和模板参数的区别: 1 2 template <int N> void func(); void func(int N); template 传入的 N,是一个编译期常量,每个不同的 N,编译器都会单独生成一份代码,从而可以对他做单独的优化;而 func(int N),则变成运行期变量,编译器无法自动优化,只能运行时得知被调用参数 N 的不同。 一个编译期优化例子是函数中需要根据debug参数控制是否输出调试信息。 如果将参数作为函数参数,那么无论是否为true,每次调用时都要进行判断,影响性能;而作为模板参数,编译器会生成两份函数func<true>和func<false>,前者保留了调试用的打印语句,后者则完全为性能优化而可以去掉打印语句。 模板的难题 编译期常量 调用模板时,填入的参数为编译期常量,不能通过运行时变量组成的表达式来指定。 可以通过constexpr关键字定义编译期常量,比如constexpr int i = 1,当然,定义时=右边的值也必须是编译期常量。 分文件编写 如果像使用传统函数一样分离函数模板的声明和定义,比如: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 /* my_sum.h */ #pragma once template <bool debug> int my_sum(int n); /* my_sum.cpp */ #include <iostream> #include "my_sum.h" template <bool debug> int my_sum(int n) { int res = 0; for (int i = 1; i <= n; i++) { res += i; } if constexpr (debug) { std::cout << res << std::endl; } return res; } /* main.cpp */ #include <iostream> #include "my_sum.h" int main() { std::cout << my_sum<true>(4) << std::endl; return 0; } 此时编译则会报如下错误: 1 undefined reference to `int my_sum<true>(int)' 这是因为编译器对模板的编译是惰性的,只有在定义模板的.cpp文件中去使用该模板时,该模板才会被真正实例化。 在上述例子中,my_sum.cpp由于并未使用到定义的函数模板,所以单独编译后并没有函数符号int my_sum<true>(int),所以在链接阶段main.cpp中调用时找不到相应的函数实现,所以会报链接错误。 解决办法:在定义模板的.cpp文件增加显式编译模板的声明,比如上述例子可以加一行template int my_sum<true>(int);。 可以看出,我们每在main.cpp中使用一款“新的”模板实例,都需要显式地去在定义处更改,使得编译出这个实例,这显然不合理。所以一般使用模板时会将声明和定义一起放在头文件中,引用时就相当于将整份代码复制过来,调用时在产生新的模板实例。 从这一点也能知道,stl头文件不是像以前C中的头文件一样,只有声明,在编译时链接动态库;而是将如何定义的全写在头文件中了。 模板元编程 Metaprogramming is the writing of computer programs: That write or manipulate other programs (or themselves) as their data, or That do… work at compile time that would otherwise be done at runtime” C++ Template Metaprogramming uses template instantiation to drive compile-time evaluation: When we use the name of a template where a (function, type, variable) is expected, the compiler will instantiate (create) the expected entity from that template. 模板元编程能够带来以下好处: 提高源代码的灵活性 提高运行时性能 使用模板元编程时,有一些限制需要注意: 不可变性(No Mutability) 无虚函数(No Virtual Functions) 无运行时类型信息(No RTTI) 总结来说,模板元编程的核心思想是将工作从运行时转移到编译时。 示例:编译时计算绝对值(Compile-time Absolute Value) 下面是一个简单的编译时绝对值元函数(metafunction)的实现: 1 2 3 4 5 template <int N> struct my_abs { static_assert(N != INT_MIN); // 避免绝对值计算溢出 static constexpr int value = (N < 0) ? -N : N; }; 这个元函数功能类似于一个constexpr函数: 1 constexpr int abs(int N) { return (N < 0) ? -N : N; } 但作为struct,元函数提供了更多的功能,例如: Public member type declarations (e.g., typedef or using). Public member data declarations (static const/constexpr, each initialized via a constant expression). Public member function declarations and constexpr member function definitions. Public member templates, static_asserts, and more! 示例:编译时递归与特化 另一个编译时数值计算的例子是计算最大公约数(GCD)。我们可以通过编译时递归和特化来实现这一点。 1 2 3 4 5 6 7 8 9 10 11 // primary template template <unsigned M, unsigned N> struct my_gcd { static constexpr int value = my_gcd<N, M % N>::value; }; // partial specialization 部分特化作为递归的base template <unsigned M> struct my_gcd<M, 0> { static_assert(M != 0); static constexpr int value = M; }; 解释: 模板的定义:我们需要先在template后给出实例化时的参数,随后可以定义相应的函数模板、类模板等。 模板特化:特化模板需要放在已定义的模板之后。在template后给出尚未确定的参数,在模板名称后用<>按照primary template定义的格式进行实例化,并给出特定情况下的模板定义。 模板的使用本质上是模板实例化,就像函数调用一样将“实参”填入形参。这类似于模式匹配,当出现多个匹配时,特化模板的匹配优先级较高,最终选择最“特别”的模板来进行实例化。因此,多个特化模板都位于主模板的范围内,但每个实例化就像if语句一样,而主模板则作为最后的默认情况。 将type作为参数 sizeof 是 C++ 提供的一个接受 type 作为参数的内置操作符。类似地,我们可以通过编写自定义的元函数,来实现类型相关的操作。 示例:获取array类型的rank 1 2 3 4 5 6 7 8 9 10 // primary template template <class T> struct rank { static constexpr size_t value = 0u; }; // partial specialization template <class T, size_t N> struct rank<T[N]> { static constexpr size_t value = 1u + rank<T>::value; }; 该例子说明: 可以将type作为参数,存在type metafunction 递归操作不仅可以在主模板(primary)中进行,也可以在模板特化(specialization)中进行。 将type作为结果 许多元函数需要一个或多个type,然后返回一个type。 示例:返回移除掉const的相同类型 1 2 3 4 5 6 7 8 9 10 11 12 13 // primary template <class T> struct remove_const { using type = T; }; // partial specialization template <class T> struct remove_const<T const> { using type = typename remove_const<T>::type; }; // alias template <class T> using remove_const_t = typename remove_const<T>::type; C++ 元函数的约定#1:struct中的type 元函数有类型结果的话用type命名 示例:An identity metafunction 1 2 3 4 5 // 返回输入类型的类型 template <class T> struct type_is { using type = T; }; 可以通过继承(inheritance)使用上面的简单元函数(以之前移除const为例): 1 2 3 4 5 6 template <class T> struct remove_const : type_is<T> {}; template <class T> struct remove_const<T const> : type_is<T> {}; template <class T> using remove_const_t = typename remove_const<T>::type; Compile-time decision-making 如果一个元函数可以根据某个条件在编译期返回不同的类型,比如 1 2 template <bool p, class T, class F> struct IF : type_is<...> {}; // p ? T : F 那么可以让我们写出self-configuring code: 1 2 3 4 5 6 7 8 // 比如代码中有一个常量q constexpr int q = ...; // k 被声明为两种类型之一 IF_t<(q < 0), int, unsigned> k; //调用两个函数之一 IF_t<(q < 0), F, G>{}(...) // 继承两个基类之一 class C: public IF_t<(q < 0), B1, B2> {...}; IF可以被实现如下: 1 2 3 4 5 6 // primary template <bool p, class T, class F> struct IF : type_is<T> {}; // specialization template <class T, class F> struct IF<false, T, F> : type_is<F> {}; 而对于单类型的判断,也就是“如果true,返回该类型;否则,无返回”,则实现如下: 1 2 3 4 5 6 7 8 // primary template <bool p, class T = void> struct enable_if : type_is<T> {}; // specialization template <class T> struct enable_if<false, T> {}; template <bool p, class T = void> using enable_if_t = typename enable_if<p, T>::type; 此时,若实例化时匹配到特化部分,也就是发生enable_if<false, ...>::type,也不一定就会发生编译错误,这就是SFINAE特性。 SFINAE SFINAE: Substitution Failure is Not An Error. 在模板实例化时,也就是发生模板调用时,会发生: 获取模板参数: 调用时直接给出 函数模板可以通过函数参数来推断 使用模板默认参数 对相应的模板占位参数进行替换 此时,如果得到正确的代码,则实例化成功;但如果结果代码是不合法的(视为替换失败(Substitution Failure)),则会被静默丢掉(be silently discarded),继续寻找下一个。 应用SFINAE的例子: 1 2 3 4 5 6 7 8 9 10 template <class T> enable_if_t<std::is_integral_v<T>, int> f(T val) { std::cout << "using int" << std::endl; return val; } template <class T> enable_if_t<std::is_floating_point_v<T>, float> f(T val) { std::cout << "using float" << std::endl; return val; } 对于以上函数模板,在调用f<>()时,如果val是整型,则会调用第一个;若是浮点型,则会调用第二个;若都不是(比如字符串),这时,两个模板都替换失败,才会报出编译器错误。 C++20出现了concept,它可以去除上述“别扭”的写法,上述的目的就是在函数调用时,根据参数类型的不同来实例化不同的模板实现,做法则是在一个实现中通过元函数来保证只有类型满足某某条件后才能生成正确的代码,让不满足的出错继而另寻他路,也就是对类型本身的限制条件。可以把这些限制和约束抽象成concept,那写法变成如下: 1 2 3 4 5 6 7 8 9 10 // 原写法:通过enable_if_t制造限制 template <class T> enable_if_t<std::is_integral_v<T>, int> f(T val) { ... }; // 新写法:定义一个concept `Integral`,写出受限的模板实现 template <Integral T> int f(T val) { ... }; C++ 元函数的约定#2:struct中的value A metafunction with a value result has: A static constexpr member, value, giving its result, and… A few convenience member types and constexpr functions. 一个规范的value-returning元函数如下: 1 2 3 4 5 6 template <class T, T v> struct integral_constant { static constexpr T value = v; constexpr operator T() const noexcept { return value; } constexpr T operator()() const noexcept { return value; } }; 其他value-returning元函数可以继承该类。 将原先的rank改写如下: 1 2 3 4 5 6 template <class T> struct rank : integral_constant<size_t, 0u> {}; template <class T, size_t N> struct rank<T[N]> : integral_constant<size_t, 1u + rank<T>::value> {}; template <class T> struct rank<T[]> : integral_constant<size_t, 1u + rank<T>::value> {}; 一些派生的类型别名: 1 2 3 4 5 template <bool b> using bool_constant = integral_constant<bool, b>; using true_type = bool_constant<true>; using false_type = bool_constant<false>; value-returning元函数的不同调用方法: 1 2 3 4 is_void<T>::value bool(is_void<T>{}) // instantiate/cast is_void<T>{}() // instantiate/call is_void_v<T> 同时利用继承、特化的一些例子:给一个类型,判断是否是 void 1 2 3 4 5 6 7 8 9 10 11 12 // primary template <class T> struct is_void : false_type {}; // specialization 共4种 template <> struct is_void<void> : true_type {}; template <> struct is_void<void const> : true_type {}; template <> struct is_void<void volatile> : true_type {}; template <> struct is_void<void const volatile> : true_type {}; 除了这种方式,也可以委托给其他元函数来实现这个需求。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 /* 给两种类型,判断是否相同 */ // primary template <class T, class U> struct is_same : false_type {}; // specialization template <class T> struct is_same<T, T> : true_type {}; /* 移除 const 和 volatile */ template<class T> using remove_cv = remove_volatile<remove_const_t<T>>; template<class T> using remove_cv_t = typename remove_cv<T>::type; /* 利用上述元函数实现 is_void */ template<class T> using is_void = is_same<remove_cv_t<T>, void>; 在元函数中使用参数包(parameter pack) 一些元函数中,我需要任意长的参数列表(比如需要任意多个类型参数),此时可以借助参数包实现。 示例:判断某个类型是否与一堆类型中的某个相同 1 2 3 4 5 6 7 8 9 10 11 12 // primary template <class T, class... P0toN> struct is_one_of; // base #1: specialization recognizes empty list of types template <class T> struct is_one_of<T> : false_type {}; // base #2: specialization recognizes match at head of list of types template <class T, class... P1toN> struct is_one_of<T, T, P1toN...> : true_type {}; // base #3: specialization recogniazes mismatch at head of list of types template <class T, class P0, class... P1toN> struct is_one_of<T, P0, P1toN...> : is_one_of<T, P1toN...> {}; 可以再次实现is_void: 1 2 3 4 5 6 7 template <class T> using is_void = is_one_of<T, void, void const, void volatile, void const volatile >; Unevaluated operands Operands of sizeof, typeid, decltype, and noexcept are never evaluated, not even at compile time: Implies that no code is generated (in these contexts) for such operand expressions, and… Implies that we need only a declaration, not a definition, to use a (function’s or object’s) name in these contexts. An unevaluated function call (e.g., to foo) can usefully map one type to another: decltype( foo(declval()) ) std::declval是函数模板,但只有声明,没有定义,无法真正调用它;std::declval<T>()被声明为给出类型T的右值结果(std::declval<T&>()给出左值);但因为没有定义,所以并不会真正返回,这正好可以用在这种unevaluated的场景下,它的作用是假装这里有一个这种类型的值。 此时,整体考虑上述的decltype( foo(declval<T>()) ): declval<T>()看起来是一个函数调用,但因为在decltype下,不会求值,所以假装返回了一个T类型的值(这里的重点只是有个这样的类型);其后,到函数foo,它看到传给他一些T类型,那我会返回什么类型就由decltype给出。 使用这种操作符的例子:在编译时检查一个类型是否支持拷贝赋值操作 1 2 3 4 5 6 7 8 9 10 11 12 13 template <class T> struct is_copy_assignment { private: // SFINAE template <class U, class = decltype(std::declval<U &>() = std::declval<U const &>())> static true_type try_assignment(U &&); // catch-all overload static false_type try_assignment(...); public: using type = decltype(try_assignment(std::declval<T>())); }; 这个例子中判断了能否std::declval<U &>() = std::declval<U const &>(),但也应该判断赋值结果类型是否是T&。 在没有decltype时,可以使用sizeof也构造出一个Unevaluated的场景,但这时type无法由decltype得到,为了得到type,可以让两种函数返回不同大小的值,然后再用sizeof判断。 1 2 3 4 5 6 7 // 定义两种返回类型 typedef char(&yes)[1]; typedef char(&no)[2]; // 得到返回值的大小 sizeof(try_assignment(std::declval<T>())) // 得到type typedef bool_constant<sizeof(try_assignment(std::declval<T>())) == sizeof(yes)> type; void_t 1 2 template <class...> using void_t = void; 对于void_t,它是void的别名,但用起来也是一个元函数调用(void_t<int, float, ...>),它接受任意数量的类型参数,给出一个void类型。 它无视了所有你给出的参数,然后如同什么都没有一样返回了一个void(就好像直接使用void),但它却可以有奇妙的用处。 示例:检测一个类中是否有一个类型成员(T::type) 1 2 3 4 5 6 // primary template <class, class = void> struct has_type_member : false_type {}; // specialization template <class T> struct has_type_member<T, void_t<typename T::type>> : true_type {}; 在该例子中,如果类型中存在类型成员type,那在调用has_type_membe<T>时,会发现特化版本has_type_member<T, void_t<typename T::type>>是合法的,尽管好像是用了一种奇怪的方法来写void,那么它将会被选中。 当然,假设不存在,那特化版本不合法,从而转向false_type。 利用这个void_t可以重写is_copy_assignment: 1 2 3 4 5 6 7 8 9 10 template <class T> using copy_assignment_t = decltype(std::declval<T &>() = std::declval<T const &>()); // primary template <class, class = void> struct is_copy_assignable : false_type {}; // specialization template <class T> struct is_copy_assignable<T, void_t<copy_assignment_t<T>>> : is_same<copy_assignment_t<T>, T &> {}; 而当把<T const &>改为<T &&>,其他不变时,就可以实现is_move_assignable。 结语 泛型编程是一种在C++中广泛使用的编程范式,它允许程序员编写与数据类型无关的代码。这种编程方式通过使用模板来实现,模板可以是函数模板或类模板。 C++ 模板最初是为实现泛型编程设计的,但人们发现模板的能力远远不止于那些设计的功能。一个重要的理论结论就是:C++ 模板是图灵完备的(可以用 C++ 模板模拟图灵机)。 理论上说 C++ 模板可以执行任何计算任务,但实际上因为模板是编译期计算,其能力受到具体编译器实现的限制(如递归嵌套深度)。C++ 模板元编程是“意外”功能,而不是设计的功能,这也是 C++ 模板元编程语法丑陋的根源。 参考: 现代模板元编程 - Cppcon 2014 - Walter E. Brown 现代C++进阶:模板元编程与函数式 关于模板元编程,这是我见过最牛的文章了!

2025/1/3
articleCard.readMore

Ubuntu包管理

dpkg dpkg是Debian的一个底层包管理工具,主要用于对已下载到本地和已安装的软件包进行管理。 dpkg即package manager for Debian ,是Debian和基于Debian的系统中一个主要的包管理工具,可以用来安装、构建、卸载、管理deb格式的软件包。 相关文件 /etc/dpkg/dpkg.cfg 1 2 # dpkg包管理软件的配置文件 /etc/dpkg/dpkg.cfg /var/lib/dpkg/available 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 # 记录系统中所有可用软件包的信息 # 不区分软件包的安装状态,只关注软件包的可用性。即使软件包未安装,只要它在软件源中可用,就会被记录在 available 文件中。 /var/lib/dpkg/available # 部分内容 Package: apt-utils Architecture: amd64 Version: 2.7.14build2 Priority: important Section: admin Source: apt Origin: Ubuntu Maintainer: Ubuntu Developers <ubuntu-devel-discuss@lists.ubuntu.com> Original-Maintainer: APT Development Team <deity@lists.debian.org> Bugs: <url id="ctv3sgkr4djh7of56nh0" type="url" status="parsed" title="One account for everything on Ubuntu" wc="702">https://bugs.launchpad.net/ubuntu/+filebug</url> Installed-Size: 660 Depends: apt (= 2.7.14build2), libapt-pkg6.0t64 (&gt;= 2.7.14build2), libc6 (&gt;= 2.34), libdb5.3t64, libgcc-s1 (&gt;= 3.3.1), libstdc++6 (&gt;= 13.1) Filename: pool/main/a/apt/apt-utils_2.7.14build2_amd64.deb Size: 217106 MD5sum: 276cc77854ef70115f98b576faafede6 SHA1: 9f1c3f32d3cde66b6b8eec54e6218ed23f46db92 SHA256: 67270c9a71dd209d7a3ba3e9474933e98a2c8e3b0390b12f2aa64cc586a00f9a SHA512: 42220bcd5bfcb43ac0fbc9b7b72f7c0d982d4ca38bb93ddcc102e96137ca713fab490d1e0956cc63cea5245cf1f32497c1412814d23f24271ac537668ea96972 Description: package management related utility programs Task: minimal Description-md5: fa0295dc4e40dbbea6c84f614c570636 Package:... ... /var/lib/dpkg/status 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 # 记录系统中所有软件包的安装状态和配置信息。 # 关注软件包的安装状态和配置情况。例如,可以查看软件包是否已安装、是否配置完成、是否存在错误等。 /var/lib/dpkg/status # 部分内容 Package: apt-utils Status: install ok installed Priority: important Section: admin Installed-Size: 660 Maintainer: Ubuntu Developers <ubuntu-devel-discuss@lists.ubuntu.com> Architecture: amd64 Source: apt Version: 2.7.14build2 Depends: apt (= 2.7.14build2), libapt-pkg6.0t64 (>= 2.7.14build2), libc6 (>= 2.34), libdb5.3t64, libgcc-s1 (>= 3.3.1), libstdc++6 (>= 13.1) Description: package management related utility programs This package contains some less used commandline utilities related to package management with APT. . * apt-extracttemplates is used by debconf to prompt for configuration questions before installation. * apt-ftparchive is used to create Packages and other index files needed to publish an archive of Debian packages * apt-sortpkgs is a Packages/Sources file normalizer. Original-Maintainer: APT Development Team <deity@lists.debian.org> Package:... ... /var/lib/dpkg/info 1 2 3 4 5 6 # 与已安装软件包相关的配置和信息文件 # 包括配置脚本(preinst、postinst、prerm、postrm)、配置文件列表(conffiles)、文件列表(list)、触发器文件(triggers)、共享库信息文件(shlibs)、MD5 校验和文件(md5sums)等。 /var/lib/dpkg/info # 部分文件 apt-utils.list # apt-utils包安装的所有文件的路径 apt-utils.md5sums # MD5 校验和文件 /var/log/dpkg.log 1 2 # dpkg包管理软件的日志文件 /var/log/dpkg.log dpkg使用文本文件作为数据库来维护系统中软件,包括文件清单、依赖关系、软件状态等等,通常在/var/lib/dpkg目录下。 常用命令 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 # 安装`.deb`包 dpkg -i myapp_1.0_all.deb # --install # 安装一个目录下所有的`.deb`包 dpkg -R /xxx # 将`.deb`包的内容解压到系统的相应目录中,但不进行配置 dpkg --unpack myapp_1.0_all.deb # 配置软件包 dpkg --configure myapp_1.0_all.deb # 显示.deb`包内文件列表 dpkg -c myapp_1.0_all.deb # 从指定目录构建`.deb`包 dpkg -b /xxx/myapp myapp_1.0_all.deb # 显示所有已安装包,指定具体包则只显示该包的信息 dpkg -l [myapp] # --list # 显示某个包安装到系统里面的文件目录信息 dpkg -L myapp # --listfiles # 报告指定包的状态信息(`/var/lib/dpkg/status`) dpkg -s myapp # --status # 搜索某文件来自哪个包,模糊搜索 dpkg -S filename # --search # 删除软件包,但保留配置文件 dpkg -r myapp #--remove # 显示包的具体信息(`/var/lib/dpkg/available`) dpkg -p myapp # 彻底清除软件包,包括配置文件和所有相关数据 dpkg -P myapp # --purge APT APT(Advanced Packaging Tool)是建立在dpkg之上更高级别的包管理工具,会使用dpkg来执行实际的软件包安装、配置和卸载操作。 起初APT的命令都被分散在了apt-get、apt-cache和apt-config这三条命令当中,apt命令的引入就是为了解决命令过于分散的问题,它包括了apt-get、apt-cache和apt-config中最常用命令选项的集合。 尽管dpkg在软件安装过程中解决了许多问题,但它在处理依赖关系方面存在局限性,需要用户手动解决依赖问题。 而apt(Advanced Packaging Tool)则克服了这一缺陷。Linux发行版会将软件预先放置在服务器上,并分析软件的依赖关系,将这些信息记录下来。当用户需要安装软件时,apt会通过软件清单列表与本地已安装的软件数据进行比较,从而从网络服务器获取所有具有依赖关系的必要软件包。 相关文件 /etc/apt/apt.conf.d/ 1 2 3 4 5 6 7 # 存放 APT 的配置文件,比如配置自动移除不再需要的软件包的行为。 /etc/apt/apt.conf.d/ # 文件如下 $ ls apt.conf.d/ 01autoremove 15update-stamp 20auto-upgrades 50appstream 50unattended-upgrades 70debconf 01-vendor-ubuntu 20apt-esm-hook.conf 20dbus 50apt-file.conf 60icons 99update-notifier 10periodic 20archive 20packagekit 50command-not-found 60icons-hidpi /etc/apt/sources.list.d/ 1 2 3 4 5 6 7 8 # 存放软件源配置文件 /etc/apt/sources.list.d/ # 文件如下 $ ls /etc/apt/sources.list.d/ google-chrome.list vscode.list ubuntu-esm-apps.sources wezterm.list ubuntu-esm-infra.sources xmake-io-ubuntu-xmake-noble.sources ubuntu.sources zerotier.list 软件源配置文件通常以.list或.sources为扩展名。 .list文件主要用于定义传统的APT软件源。它们通常包含软件源的 URI、suite、component等信息。 1 2 # 格式简单,单行即可配置;每行写一个suite,每个suite可以有多个component deb [arch=amd64] https://dl.google.com/linux/chrome/deb/ stable main .sources文件是较新的软件源配置格式,用于更灵活地定义软件源,支持更复杂的配置选项,如签名验证、软件源的启用状态等。 1 2 3 4 5 6 # 将每个信息都写成单独一行的键值对 Types: deb URIs: https://mirrors.tuna.tsinghua.edu.cn/ubuntu Suites: noble noble-updates noble-backports Components: main restricted universe multiverse Signed-By: /usr/share/keyrings/ubuntu-archive-keyring.gpg /var/cache/apt/archives/ 1 2 # 存储从软件源下载的`.deb`包(安装后可能会被自动删除) /var/cache/apt/archives/ /var/lib/apt/lists/ 1 2 # 保存从软件源更新得到的软件列表信息 /var/lib/apt/lists/ 在该文件夹中保存有软件列表的许多信息,以从官方源(http://archive.ubuntu.com/ubuntu/)拉取到的信息为例。 每个suite级别有*InRelease文件、*Contents-*.lz4文件;每个suite下的每个component有*Packages文件、*cnf_Commands-*文件、*dep11_Components-*.yml.gz文件、*dep11_icons-*.tar.gz文件、*i18n_Translation-*等。 *InRelease:包含了软件源的发布信息,如版本号、签名等,用于验证软件源的完整性和一致性。 *Contents-*.lz4:包含了软件包内容索引,使用 lz4 压缩格式,提供了软件包文件的详细列表和位置信息。 *Packages:包含了软件包的详细信息,如包名、版本、依赖关系、描述等,是 APT 获取软件包信息的主要来源。 *cnf_Commands-*:给出了软件包提供的多个命令行操作。 *dep11_Components-*.yml.gz:包含了软件包的依赖关系信息,使用 gzip 压缩格式,帮助 APT 解析和满足软件包的依赖需求。 *dep11_icons-*.tar.gz:包含了软件包的图标文件,使用 tar 和 gzip 压缩格式。 *i18n_Translation-*:提供了软件包的本地化描述。 软件源 对官方软件源进行详细介绍,以Ubuntu22.04LTS为例。 /etc/apt/sources.list 文件如下(目前在用的Ubuntu24.04LTS已经将所有源信息的文件放入 /etc/apt/sources.list.d/ 文件夹中): 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 # See http://help.ubuntu.com/community/UpgradeNotes for how to upgrade to # newer versions of the distribution. deb http://archive.ubuntu.com/ubuntu/ jammy main restricted # deb-src http://archive.ubuntu.com/ubuntu/ jammy main restricted ## Major bug fix updates produced after the final release of the ## distribution. deb http://archive.ubuntu.com/ubuntu/ jammy-updates main restricted # deb-src http://archive.ubuntu.com/ubuntu/ jammy-updates main restricted ## N.B. software from this repository is ENTIRELY UNSUPPORTED by the Ubuntu ## team. Also, please note that software in universe WILL NOT receive any ## review or updates from the Ubuntu security team. deb http://archive.ubuntu.com/ubuntu/ jammy universe # deb-src http://archive.ubuntu.com/ubuntu/ jammy universe deb http://archive.ubuntu.com/ubuntu/ jammy-updates universe # deb-src http://archive.ubuntu.com/ubuntu/ jammy-updates universe ## N.B. software from this repository is ENTIRELY UNSUPPORTED by the Ubuntu ## team, and may not be under a free licence. Please satisfy yourself as to ## your rights to use the software. Also, please note that software in ## multiverse WILL NOT receive any review or updates from the Ubuntu ## security team. deb http://archive.ubuntu.com/ubuntu/ jammy multiverse # deb-src http://archive.ubuntu.com/ubuntu/ jammy multiverse deb http://archive.ubuntu.com/ubuntu/ jammy-updates multiverse # deb-src http://archive.ubuntu.com/ubuntu/ jammy-updates multiverse ## N.B. software from this repository may not have been tested as ## extensively as that contained in the main release, although it includes ## newer versions of some applications which may provide useful features. ## Also, please note that software in backports WILL NOT receive any review ## or updates from the Ubuntu security team. deb http://archive.ubuntu.com/ubuntu/ jammy-backports main restricted universe multiverse # deb-src http://archive.ubuntu.com/ubuntu/ jammy-backports main restricted universe multiverse deb http://security.ubuntu.com/ubuntu/ jammy-security main restricted # deb-src http://security.ubuntu.com/ubuntu/ jammy-security main restricted deb http://security.ubuntu.com/ubuntu/ jammy-security universe # deb-src http://security.ubuntu.com/ubuntu/ jammy-security universe deb http://security.ubuntu.com/ubuntu/ jammy-security multiverse # deb-src http://security.ubuntu.com/ubuntu/ jammy-security multiverse deb-src deb: deb-src: 包含软件源代码、版本修改说明、构建指令以及编译工具等。允许用户下载软件包的源代码,从而可以自己编译软件。这通常用于开发者或那些需要定制软件包或需要最新开发版本的用户。 deb: 包含可执行文件、库文件、配置文件、man/info页面、版权声明和其它文档。提供了预先编译好的软件包,用户可以直接安装而无需自己编译。 jammy: Ubuntu22.04 代号 Ubuntu 18.04 LTS bionic Ubuntu 20.04 LTS focal Ubuntu 22.04 LTS jammy Ubuntu 24.04 LTS noble Suites: jammy jammy-security jammy-backports jammy-updates jammy-proposed 基础:由于ubuntu是每6个月发行一个新版,当发行后,所有软件包的版本在这六个月内将保持不变,即使是有新版都不更新。除开重要的安全补丁外,所有新功能和非安全性补丁将不会提供给用户更新。   security:仅修复漏洞,并且尽可能少的改变软件包的行为,低风险。 backports:backports 的团队则认为最好的更新策略是 security 策略加上新版本的软件(包括候选版本的),但不会由Ubuntu security team审查和更新。 updates:包含了针对特定版本的更新,这些更新是在原始版本发布之后提供的,可能包括安全修复、bug修复和一些较小的功能改进。   proposed:updates类的测试部分,仅建议提供测试和反馈的人进行安装。 Components: main restricted universe multiverse main: 保留了Ubuntu支持的自由和开源软件。 restricted: 保留专有驱动程序(即 NVIDIA)。 universe: 保留了社区维护的免费开源软件。 multiverse: 保留有版权限制或法律问题的软件。 常用命令 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 apt [options] [command] [package ...] # 常见的options -h # 显示帮助 -y # 直接对后续询问回答“是” -f # 尝试修正系统依赖损坏处 sudo apt install -f # 从配置的源下载包信息,更新本地包索引数据库 apt update # 从配置的源安装当前系统中的所有包的可用升级 apt upgrade # 相比`apt upgrade`更激进,如果有依赖问题,需要安装或移除新的包,就会试着去安装或移除它(更有风险) apt dist-upgrade # 安装一个或多个指定的包 apt install vim # 用来删除包,但是会保留包的配置文件 apt remove vim # 用来删除包,在删除包的同时删除其配置文件 apt purge vim # 用于删除自动安装的包(满足其他包的依赖关系),由于依赖关系的更改或需要它们的包已被删除 apt autoremove # 用于在可用包列表中搜索给定的项并显示匹配到的内容 apt search vim # 显示关于给定包的信息,包括它的依赖关系、安装和下载大小、包的来源、包内容的描述等等 apt show vim # 显示满足特定条件的包列表,默认列出所有的包 apt list --upgradeable # 列出可更新的软件包及版本信息 apt list --installed # 列出已安装的软件包及版本信息 参考: 源中的 backports proposed security updates 的意思(已解决) - Ubuntu中文论坛 Linux apt 命令 | 菜鸟教程 (runoob.com) Ubuntu 软件包管理工具 —— dkpg、apt(dpkg常用指令、apt 软件源的配置)-CSDN博客 Ubuntu操作-05 Dpkg与Apt

2025/1/1
articleCard.readMore

C/C++编译知识

使用 GCC 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 kang@pro:~$ gcc --help Usage: gcc [options] file... Options: -pass-exit-codes Exit with highest error code from a phase. --help Display this information. --target-help Display target specific command line options (including assembler and linker options). --help={common|optimizers|params|target|warnings|[^]{joined|separate|undocumented}}[,...]. Display specific types of command line options. (Use '-v --help' to display command line options of sub-processes). --version Display compiler version information. -dumpspecs Display all of the built in spec strings. -dumpversion Display the version of the compiler. -dumpmachine Display the compiler's target processor. -foffload=<targets> Specify offloading targets. -print-search-dirs Display the directories in the compiler's search path. -print-libgcc-file-name Display the name of the compiler's companion library. -print-file-name=<lib> Display the full path to library <lib>. -print-prog-name=<prog> Display the full path to compiler component <prog>. -print-multiarch Display the target's normalized GNU triplet, used as a component in the library path. -print-multi-directory Display the root directory for versions of libgcc. -print-multi-lib Display the mapping between command line options and multiple library search directories. -print-multi-os-directory Display the relative path to OS libraries. -print-sysroot Display the target libraries directory. -print-sysroot-headers-suffix Display the sysroot suffix used to find headers. -Wa,<options> Pass comma-separated <options> on to the assembler. -Wp,<options> Pass comma-separated <options> on to the preprocessor. -Wl,<options> Pass comma-separated <options> on to the linker. -Xassembler <arg> Pass <arg> on to the assembler. -Xpreprocessor <arg> Pass <arg> on to the preprocessor. -Xlinker <arg> Pass <arg> on to the linker. -save-temps Do not delete intermediate files. -save-temps=<arg> Do not delete intermediate files. -no-canonical-prefixes Do not canonicalize paths when building relative prefixes to other gcc components. -pipe Use pipes rather than intermediate files. -time Time the execution of each subprocess. -specs=<file> Override built-in specs with the contents of <file>. -std=<standard> Assume that the input sources are for <standard>. --sysroot=<directory> Use <directory> as the root directory for headers and libraries. -B <directory> Add <directory> to the compiler's search paths. -v Display the programs invoked by the compiler. -### Like -v but options quoted and commands not executed. -E Preprocess only; do not compile, assemble or link. -S Compile only; do not assemble or link. -c Compile and assemble, but do not link. -o <file> Place the output into <file>. -pie Create a dynamically linked position independent executable. -shared Create a shared library. -x <language> Specify the language of the following input files. Permissible languages include: c c++ assembler none 'none' means revert to the default behavior of guessing the language based on the file's extension. Options starting with -g, -f, -m, -O, -W, or --param are automatically passed on to the various sub-processes invoked by gcc. In order to pass other options on to these processes the -W<letter> options must be used. For bug reporting instructions, please see: <file:///usr/share/doc/gcc-13/README.Bugs>. 一些参数可以单独使用,此时可以将单字母粘连,比如 -c -g 等价于 -cg; 一些参数后要跟特定的值,可以用 =、空格、粘连赋值,比如 gcc -Iinclude1 -I=include2 -I include3(但也有可能一些参数只能遵循特定的形式)。 常用的参数项 1 2 3 4 5 6 7 8 9 10 11 12 -I/xxx # 指定头文件搜索目录 -L/xxx # 指定库文件搜索目录 -l/xxx # 链接库文件 -c # 只编译和汇编,不链接,生成目标文件(.o) -S # 只编译,不汇编,生成汇编代码文件(.S) -g # 生成调试信息 -Wall # 打开几乎所有警告信息 -O<level> # 设置优化等级 -std=c11 # 指定使用的C语言标准 -fPIC # 生成位置无关代码 多文件 多文件编译,其实就是将每个文件编译后的 .o 文件链接后的产物,所以在 make 中会像下面一样,将每个部分单独写出来,这样修改时未被更改的部分无需重复编译(可以利用通配符在 Makefile 中进一步进行抽象)。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 main: main.o hello.o gcc main.o hello.o -o main main.o: main.c gcc -c main.c -o main.o hello.o: hello.c gcc -c hello.c -o hello.o # 进一步抽象描述 CC = gcc SRC = main.c hello.c OBJ = $(SRC:.c=.o) main: $(OBJ) $(CC) $^ -o $@ %.o: %.c $(CC) -c $^ -o $@ #include 如果是 "",则会先在当前目录查找,失败后才会在系统路径查找; 如果是 <>,则直接在系统路径中查找。 在编译时指定的 -I,与系统目录等价,所以也可以被 <> 发现,借此避免使用冗长的相对路径格式。 虽然编译时正确找到头文件,但是代码补全插件找不到,写代码不舒服。不过既然编译没问题,可以在编译时将头文件目录这种要求搞成一个配置文件,也就是 compile_commands.json 文件。直接通过 Makefile 构建的,可以通过 Bear 生成该文件;通过 CMake 配置的,可以加一个配置选项 -DCMAKE_EXPORT_COMPILE_COMMANDS=ON,在 build 目录即可生成该文件。 链接库 下载使用第三方库 一般引用第三方库是通过包管理器(apt 等)下载相应的包,由于 apt 会下载到系统路径,所以编写代码时可以直接 #include 相应第三方库的头文件,而在编译时同样需要链接第三方库。 因为头文件的作用是让我们复制一边声明,真正的库实现需要编译时手动链接。 比如在 C++ 中使用 fmt 库,通过 -lfmt 链接该库。 因为链接时还需要添加上具体的库名,不手动去看并不知道有哪些,此时可以通过 pkg-config 获取一个包的库名字等。 1 2 3 4 5 6 7 # 获得库的链接参数 kang@pro:~$ pkg-config libgvc --libs -lgvc -lcgraph -lcdt # 获得头文件目录的链接参数 kang@pro:~$ pkg-config libgvc --cflags -I/usr/include/graphviz 以上返回是由包中的 pkgconfig 目录中的内容决定的。 链接多个 .o 文件和库文件时,放置顺序会影响结果。目标文件的未定义符号,只会在它后面的目标文件或库中寻找,不会再看前面的。所以,被依赖的要放在后面。 包的组成 一个第三方库大致有以下部分: 1 2 3 4 5 6 # 有些本身是工具库存在的可能没有 bin/; 同样的,一些主要提供可执行程序直接使用的可能没有 include/ .|- bin # 可执行文件 |- include # 头文件 |- lib # 静态库和动态库 |_ share # 文档等 而直接安装一个第三方库,比如 Ubuntu 下的 deb 包,安装后以上文件会自动放入对应的系统路径 /usr/xxx 中,而在编译器编译时,这些路径会进行默认搜索,所以头文件可以直接找到,库文件也直接写名字即可链接。 而如果放在一个一般路径,在编译时则需要指定相应目录。 1 2 # -I 指定头文件目录; -L 指定库文件目录; -l 指定库名字 g++ main.c -I/xxx/include -L/xxx/lib -lfmt -o main 对于 gcc,可以通过以下命令查看默认搜索的头文件目录和库目录。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 # 打印出gcc自身安装目录和库搜索目录等 kang@pro:~$ gcc -print-search-dirs install: /usr/lib/gcc/x86_64-linux-gnu/13/ programs: =/usr/libexec/gcc/x86_64-linux-gnu/13/:/usr/libexec/gcc/x86_64-linux-gnu/13/:/usr/libexec/gcc/x86_64-linux-gnu/:/usr/lib/gcc/x86_64-linux-gnu/13/:/usr/lib/gcc/x86_64-linux-gnu/:/usr/lib/gcc/x86_64-linux-gnu/13/../../../../x86_64-linux-gnu/bin/x86_64-linux-gnu/13/:/usr/lib/gcc/x86_64-linux-gnu/13/../../../../x86_64-linux-gnu/bin/x86_64-linux-gnu/:/usr/lib/gcc/x86_64-linux-gnu/13/../../../../x86_64-linux-gnu/bin/ libraries: =/usr/lib/gcc/x86_64-linux-gnu/13/:/usr/lib/gcc/x86_64-linux-gnu/13/../../../../x86_64-linux-gnu/lib/x86_64-linux-gnu/13/:/usr/lib/gcc/x86_64-linux-gnu/13/../../../../x86_64-linux-gnu/lib/x86_64-linux-gnu/:/usr/lib/gcc/x86_64-linux-gnu/13/../../../../x86_64-linux-gnu/lib/../lib/:/usr/lib/gcc/x86_64-linux-gnu/13/../../../x86_64-linux-gnu/13/:/usr/lib/gcc/x86_64-linux-gnu/13/../../../x86_64-linux-gnu/:/usr/lib/gcc/x86_64-linux-gnu/13/../../../../lib/:/lib/x86_64-linux-gnu/13/:/lib/x86_64-linux-gnu/:/lib/../lib/:/usr/lib/x86_64-linux-gnu/13/:/usr/lib/x86_64-linux-gnu/:/usr/lib/../lib/:/usr/lib/gcc/x86_64-linux-gnu/13/../../../../x86_64-linux-gnu/lib/:/usr/lib/gcc/x86_64-linux-gnu/13/../../../:/lib/:/usr/lib/ # -E 预处理; -v 详细输出; - 从标准输入读取; # 用一个空内容,查看预处理阶段会进行哪些操作,可以发现默认的头搜索目录 kang@pro:~$ gcc -E -v - < /dev/null Using built-in specs. COLLECT_GCC=gcc OFFLOAD_TARGET_NAMES=nvptx-none:amdgcn-amdhsa OFFLOAD_TARGET_DEFAULT=1 Target: x86_64-linux-gnu Configured with: ../src/configure -v --with-pkgversion='Ubuntu 13.2.0-23ubuntu4' --with-bugurl=file:///usr/share/doc/gcc-13/README.Bugs --enable-languages=c,ada,c++,go,d,fortran,objc,obj-c++,m2 --prefix=/usr --with-gcc-major-version-only --program-suffix=-13 --program-prefix=x86_64-linux-gnu- --enable-shared --enable-linker-build-id --libexecdir=/usr/libexec --without-included-gettext --enable-threads=posix --libdir=/usr/lib --enable-nls --enable-clocale=gnu --enable-libstdcxx-debug --enable-libstdcxx-time=yes --with-default-libstdcxx-abi=new --enable-libstdcxx-backtrace --enable-gnu-unique-object --disable-vtable-verify --enable-plugin --enable-default-pie --with-system-zlib --enable-libphobos-checking=release --with-target-system-zlib=auto --enable-objc-gc=auto --enable-multiarch --disable-werror --enable-cet --with-arch-32=i686 --with-abi=m64 --with-multilib-list=m32,m64,mx32 --enable-multilib --with-tune=generic --enable-offload-targets=nvptx-none=/build/gcc-13-uJ7kn6/gcc-13-13.2.0/debian/tmp-nvptx/usr,amdgcn-amdhsa=/build/gcc-13-uJ7kn6/gcc-13-13.2.0/debian/tmp-gcn/usr --enable-offload-defaulted --without-cuda-driver --enable-checking=release --build=x86_64-linux-gnu --host=x86_64-linux-gnu --target=x86_64-linux-gnu Thread model: posix Supported LTO compression algorithms: zlib zstd gcc version 13.2.0 (Ubuntu 13.2.0-23ubuntu4) COLLECT_GCC_OPTIONS='-E' '-v' '-mtune=generic' '-march=x86-64' /usr/libexec/gcc/x86_64-linux-gnu/13/cc1 -E -quiet -v -imultiarch x86_64-linux-gnu - -mtune=generic -march=x86-64 -fasynchronous-unwind-tables -fstack-protector-strong -Wformat -Wformat-security -fstack-clash-protection -fcf-protection -dumpbase - ignoring nonexistent directory "/usr/local/include/x86_64-linux-gnu" ignoring nonexistent directory "/usr/lib/gcc/x86_64-linux-gnu/13/include-fixed/x86_64-linux-gnu" ignoring nonexistent directory "/usr/lib/gcc/x86_64-linux-gnu/13/include-fixed" ignoring nonexistent directory "/usr/lib/gcc/x86_64-linux-gnu/13/../../../../x86_64-linux-gnu/include" #include "..." search starts here: #include <...> search starts here: /usr/lib/gcc/x86_64-linux-gnu/13/include /usr/local/include /usr/include/x86_64-linux-gnu /usr/include End of search list. # 0 "<stdin>" # 0 "<built-in>" # 0 "<command-line>" # 1 "/usr/include/stdc-predef.h" 1 3 4 # 0 "<command-line>" 2 # 1 "<stdin>" COMPILER_PATH=/usr/libexec/gcc/x86_64-linux-gnu/13/:/usr/libexec/gcc/x86_64-linux-gnu/13/:/usr/libexec/gcc/x86_64-linux-gnu/:/usr/lib/gcc/x86_64-linux-gnu/13/:/usr/lib/gcc/x86_64-linux-gnu/ LIBRARY_PATH=/usr/lib/gcc/x86_64-linux-gnu/13/:/usr/lib/gcc/x86_64-linux-gnu/13/../../../x86_64-linux-gnu/:/usr/lib/gcc/x86_64-linux-gnu/13/../../../../lib/:/lib/x86_64-linux-gnu/:/lib/../lib/:/usr/lib/x86_64-linux-gnu/:/usr/lib/../lib/:/usr/lib/gcc/x86_64-linux-gnu/13/../../../:/lib/:/usr/lib/ COLLECT_GCC_OPTIONS='-E' '-v' '-mtune=generic' '-march=x86-64' 对于开源的第三方库(以及要编写自己的开源项目的话),可以通过更改配置来自定义编译的结果。在编译很多项目时,往往会经历 configure->make->make install 三部曲(很像使用 CMake 的流程,相比之下 CMake 新多了)。 在用户视角下,首先我们要运行开发者提供的 configure 脚本,如下图所示,这个过程会生成 Makefile 文件,用户可以选择加参数自定义配置,比如 ./configure -prefix=/xxx; 随后,利用 Makefile 文件进行构建,并将软件安装到对应的位置。 开发者可以使用 GNU 构建系统(GNU Build System)来构建支持以上流程的软件体系,包含三件套:autoconf, antomake 和 libtool. 运行时库 为了提升C语言的开发效率,C语言标准定义了一系列常用的函数,这些函数统称为C库函数。C标准仅规定了这些函数的原型,而具体的实现则留给了各个支持C语言标准的编译器。因此,每个编译器通常会提供超出标准C的实现,形成了所谓的C运行时库(C Run Time Library)。 C运行时库中的启动函数负责程序的初始化工作,包括设置堆栈、初始化全局和静态变量、调用构造函数等;程序结束时,运行时库也负责调用析构函数、释放资源等清理工作。所以,C程序都需要链接C运行时库。 与C语言类似,C++也有自己的标准,并提供了相应的支持库,即C++运行时库或C++标准库。由于C++设计时考虑了与C语言的兼容性,C++标准库不仅包含了C标准库,还扩展了包括标准模板库(STL)在内的更多功能。 通过 ldd 可以列出可执行文件或共享库所依赖的共享库。 可以看出,默认情况下一个简单的C程序会动态链接 libc.so.6,我们也可以指定静态链接,此时无需依赖运行时库,但文件体积也相应增大。 参考: 【技术杂谈】C编译体系 GNU构建系统和Autotool 终于理解了什么是c/c++运行时库,以及libcmt msvcrt等内容

2024/12/9
articleCard.readMore

如何使用 CMake

CMake 配置构建 流程 1 2 3 4 5 6 # 创建 build 文件夹并进行配置(configure) cmake -B build # 根据生成的构建工具进行构建(build)(统一了不同平台的不同构建工具的命令) cmake --build build -j4 # 安装(可以类比 make install, 构建 install 目标) sudo cmake --build build --target install 配置阶段的参数 -G 指定生成器,CMake 可以生成不同类型的构建系统(比如 Makefile MSBuild,所以可以跨平台) cmake -B build -G "Unix Makefiles" -D 指定配置变量,配置后会保存在build/CMakeCache.txt中,下次配置仍会保留之前设置的值(删除缓存信息可以只删除CMakeCache.txt而非整个build)。 1 2 3 cmake -B build -DCMAKE_INSTALL_PREFIX=/opt/myapp # 设置安装路径 cmake -B build -DCMAKE_BUILD_TYPE=Release # 设置构建模式为发布模式 cmake -B build -DCMAKE_EXPORT_COMPILE_COMMANDS=ON # 设置生成 compile_commands.json 文件 CMake 文件 模板 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 cmake_minimum_required(VERSION 3.15) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_EXTENSIONS OFF) project(prj LANGUAGES C CXX) if (PROJECT_BINARY_DIR STREQUAL PROJECT_SOURCE_DIR) message(WARNING "The binary directory of CMake cannot be the same as source directory!") endif() if (NOT CMAKE_BUILD_TYPE) set(CMAKE_BUILD_TYPE Release) endif() if (WIN32) add_definitions(-DNOMINMAX -D_USE_MATH_DEFINES) endif() if (NOT MSVC) find_program(CCACHE_PROGRAM ccache) if (CCACHE_PROGRAM) message(STATUS "Found CCache: ${CCACHE_PROGRAM}") set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE ${CCACHE_PROGRAM}) set_property(GLOBAL PROPERTY RULE_LAUNCH_LINK ${CCACHE_PROGRAM}) endif() endif() 生成可执行程序 1 2 3 4 5 6 7 8 9 10 add_executable(main main.cpp hello.cpp) # 先指定可执行程序,后添加 add_executable(main) target_sources(main PUBLIC main.cpp hello.cpp) # 使用 GLOB 根据扩展名批量查找,替换成 GLOB_RECURSE 则会包含所有子文件夹中的匹配,CONFIGURE_DEPENDS 保证增减文件后自动更新变量 add_executable(main) file(GLOB sources CONFIGURE_DEPENDS *.cpp *.h) target_sources(main PUBLIC ${sources}) 生成库 1 2 3 4 5 6 7 8 # 静态库 add_library(mylib STATIC mylib.cpp) # 动态库 add_library(mylib SHARED mylib.cpp) # OBJ库 add_library(mylib OBJECT mylib.cpp) 项目配置变量 CMAKE_BUILD_TYPE: 构建类型 Debug 调试模式,生成调试信息 Release 发布模式,优化程度最高 MinSizeRel 最小体积发布,生成的文件比 Release 更小 RelWithDebInfo 带调试信息发布 与 project 相关的变量 project(helloprj) PROJECT_SOURCE_DIR 若无project,向上一级找,找到最近的调用project的 CMakeLists.txt 所在的源码目录;也就是找到字意的项目目录,从子模块里直接获得项目最外层目录的路径。 CMAKE_CURRENT_SOURCE_DIR 当前 CMakeLists.txt 所在的源码目录。 CMAKE_SOURCE_DIR 最外层 CMakeLists.txt 的源码根目录,不建议使用,若项目作为别人的子项目则会直接代表调用项目的根目录。 PROJECT_BINARY_DIR 与PROJECT_SOURCE_DIR对应,是二进制产物路径。 CMAKE_BINARY_DIR 与CMAKE_SOURCE_DIR对应,是二进制产物路径。 PROJECT_NAME 当前项目名 CMAKE_PROJECT_NAME 根项目项目名 C++ 一些要求的配置 1 2 3 set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_EXTENSIONS OFF) # 设置是否启用 GCC 特有的功能,关闭以兼容其他编译器 target的相关描述 target的一些属性也有相应的全局变量,改变全局变量相当于改变了各个属性的初始默认值。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 # 设置target的属性 add_executable(main main.cpp) set_property(TARGET main PROPERTY CXX_STANDARD 17) # 设置 C++ 标准 set_property(TARGET main PROPERTY CXX_STANDARD_REQUIRED ON) # 编译器不支持则报错 set_property(TARGET main PROPERTY WIN32_EXECUTABLE ON) # 在 Windows 系统中,运行时不启动控制台窗口 set_property(TARGET main PROPERTY LINK_WHAT_YOU_USE ON) # 告诉编译器不要自动剔除没有引用符号的链接库 set_property(TARGET main PROPERTY LIBRARY_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}/lib) # 设置动态链接库的输出路径 set_property(TARGET main PROPERTY ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}/lib) # 设置静态链接库的输出路径 set_property(TARGET main PROPERTY RUNTIME_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}/bin) # 设置可执行文件的输出路径 # 批量设置 add_executable(main main.cpp) set_target_properties(main PROPERTIES CXX_STANDARD 17 # 设置 C++ 标准 CXX_STANDARD_REQUIRED ON # 编译器不支持则报错 WIN32_EXECUTABLE ON # 在 Windows 系统中,运行时不启动控制台窗口 LINK_WHAT_YOU_USE ON # 告诉编译器不要自动剔除没有引用符号的链接库 LIBRARY_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}/lib # 设置动态链接库的输出路径 ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}/lib # 设置静态链接库的输出路径 RUNTIME_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}/bin # 设置可执行文件的输出路径 ) # 设置相应的全局变量 set(CMAKE_CXX_STANDARD 17) # 设置 C++ 标准 set(CMAKE_CXX_STANDARD_REQUIRED ON) # 编译器不支持则报错 set(CMAKE_WIN32_EXECUTABLE ON) # 在 Windows 系统中,运行时不启动控制台窗口 set(CMAKE_LINK_WHAT_YOU_USE ON) # 告诉编译器不要自动剔除没有引用符号的链接库 set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}/lib) # 设置动态链接库的输出路径 set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}/lib) # 设置静态链接库的输出路径 set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}/bin) # 设置可执行文件的输出路径 add_executable(main main.cpp) 使用针对target的选项(头文件搜索目录等),避免添加到所有target 1 2 3 4 5 6 7 8 9 10 11 target_sources(myapp PUBLIC hello.cpp other.cpp) # 添加源文件 target_include_directories(myapp PUBLIC include) # 添加头文件搜索目录 target_link_libraries(myapp PUBLIC hellolib) # 添加链接库 target_add_definitions(myapp PUBLIC -DMY_MACRO=1) # 添加宏定义 MY_MACRO=1 target_compile_options(myapp PUBLIC -fopenmp) # 添加编译选项 # 避免使用 include_directories(include) # 添加头文件搜索目录 link_directories(/opt/cuda) # 添加链接库搜索目录 add_definitions(MY_MACRO=1) # 添加宏定义 MY_MACRO=1 add_compile_options(-fopenmp) # 添加编译选项 第三方库引入方法 作为纯头文件引入 target_include_directories 适用于那些只有头文件的库,例如一些轻量级的模板库。这些库不需要编译,因为它们的实现代码都在头文件中,通常是通过模板或者宏等方式实现功能。比如C++的标准模板库就是纯头文件。 以fmt库为例,该库介绍说明可以通过纯头文件引入,此时只需要项目中的include文件夹。 按照要求,在纯头文件引入时需要定义FMT_HEADER_ONLY,此时项目结构如下: 在CMakeLists.txt中通过target_include_directories引入第三方库头文件目录。 1 2 3 4 5 6 7 cmake_minimum_required(VERSION 3.30) project(prj) set(CMAKE_CXX_STANDARD 20) add_executable(prj main.cpp) target_include_directories(prj PUBLIC include) 但是直接引入头文件,函数实现在头文件里,没有提前编译,每次需要重复编译同样的内容,编译时间长。 作为子模块引入 add_subdirectory 这种方式将第三方库的源代码直接包含到项目中,第三方库通常有自己的CMakeLists.txt文件,通过add_subdirectory指令,可以将这个库的构建过程集成到主项目的构建过程中。 以fmt库为例,这个开源库可以直接将该项目作为用户项目的子项目引入,直接clone源码,目录结构如下: 在CMakeLists.txt中通过add_subdirectory引入第三方库的项目子目录,再通过target_link_libraries链接第三方项目库。 1 2 3 4 5 6 7 8 9 cmake_minimum_required(VERSION 3.30) project(prj) set(CMAKE_CXX_STANDARD 20) add_subdirectory(fmt) add_executable(prj main.cpp) target_link_libraries(prj fmt::fmt) FetchContent FetchContent 是 CMake 的一个模块,可以在配置阶段获取外部依赖库,允许配置步骤使用这些内容进行 add_subdirectory()、include() 或 file() 操作。 这与上文所述基本相同,都是直接将第三方项目引入,但通过 FetchContent 可以直接将依赖项目写在 CMakeLists.txt 中,在配置阶段从远程库中下载依赖项目,而无需手动下载。 FetchContent_Declare FetchContent_Declare() 函数用于指定如何获取外部项目,比如仓库地址等。 1 2 3 4 5 6 7 8 FetchContent_Declare( <name> <contentOptions>... [EXCLUDE_FROM_ALL] [SYSTEM] [OVERRIDE_FIND_PACKAGE | FIND_PACKAGE_ARGS args...] ) FetchContent_MakeAvailable FetchContent_MakeAvailable命令确保依赖项已经被获取。在获取时,它还会将它们添加到主构建中,以便主构建可以使用这些项目的目标等。 1 FetchContent_MakeAvailable(googletest) 具体使用步骤: 包含FetchContent模块 1 include(FetchContent) 声明外部项目 1 2 3 4 5 FetchContent_Declare( fmt GIT_REPOSITORY https://github.com/fmtlib/fmt.git GIT_TAG 11.1.1 ) 这一步声明了一个外部项目fmt,并指定了其下载和配置的详细信息(此时并不会立即下载或配置项目)。 确保外部项目可用 1 FetchContent_MakeAvailable(fmt) 这一步确保声明的外部项目fmt已经被下载、配置、构建,并且可以使用。 链接第三方库 1 target_link_libraries(prj PRIVATE fmt::fmt) 这一步使用 target_link_libraries 将第三方库 fmt 链接到项目中。 使用该种途径时,项目如下,可以发现build文件夹中的_deps文件夹存放了获取的第三方项目,在main.cpp中可以直接使用。 在CMakeLists.txt中通过FetchContent引入第三方项目,再通过target_link_libraries链接第三方项目库。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 cmake_minimum_required(VERSION 3.30) project(prj) set(CMAKE_CXX_STANDARD 20) include(FetchContent) FetchContent_Declare(fmt GIT_REPOSITORY https://github.com/fmtlib/fmt.git GIT_TAG 11.1.1) FetchContent_MakeAvailable(fmt) add_executable(prj main.cpp) target_link_libraries(prj PUBLIC fmt::fmt) 引用系统中安装的第三方库 find_package 在存在菱形依赖的情况下,即项目A依赖于B和C,而B和C又同时依赖于D,使用子模块引用(add_subdirectory),可能会导致D被定义两遍,从而引发错误。 而通过find_package使用系统预安装的库则可以有效避免这个问题。当使用find_package查找库时,CMake会记录已经找到的库。因此,即使多个模块依赖同一个库,find_package也只会引入一次。例如,当找到库B和D时,再找C时不会将D重复引入。 不同操作系统可以通过各自的包管理器来安装所需的库。以Ubuntu为例,可以使用apt包管理器来安装库。比如安装fmt库: 1 sudo apt install libfmt-dev 此时由于头文件等已经在系统查找路径中(比如/usr/include),可以直接在文件中导入相关的头文件,此时,项目结构如下: 在CMakeLists.txt中则需要先find_package找到fmt包,再通过target_link_libraries链接第三方项目库。 1 2 3 4 5 6 7 8 9 cmake_minimum_required(VERSION 3.30) project(prj) set(CMAKE_CXX_STANDARD 20) find_package(fmt) add_executable(prj main.cpp) target_link_libraries(prj fmt::fmt) 在CMake中,一个项目可以包含多个库。CMake允许一个包(package)提供多个库,这些库也被称为组件(components)。因此,在使用target_link_libraries指令链接库时,应采用包名::组件名的格式。 例如,在上文中提到的fmt::fmt,其中fmt是包名,第二个fmt是该包提供的一个组件名。再比如,TBB这个包,就包含了tbb、tbbmalloc和tbbmalloc_proxy这三个组件。当需要链接这些组件时,可以分别使用TBB::tbb、TBB::tbbmalloc和TBB::tbbmalloc_proxy。 find_package时可以指定必要的组件: 1 2 find_package(TBB REQUIRED COMPONENTS tbb tbbmalloc REQUIRED) target_link_libraries(myexec PUBLIC TBB::tbb TBB::tbbmalloc) CMake 项目结构 一个典型的 C++ 项目可以采用以下结构: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 prj ├── CMakeLists.txt ├── cmake │ └── MyFuncs.cmake ├── subprj1 │ ├── CMakeLists.txt │ ├── include │ │ └── subprj1 │ │ └── Animal.h │ └── src │ └── Animal.cpp ├── ... └── mainprj ├── CMakeLists.txt ├── include │ └── mainprj │ └── utils.h └── src └── main.cpp 在这个结构中,项目根目录包含了一个 CMakeLists.txt 文件以及多个子项目文件夹。 一个子项目作为可执行文件,负责与用户交互,其他子项目则作为不同的库文件,编写实际的业务逻辑。可执行文件仅作为入口,所有的功能实现都在库文件中,这样的分离使得代码逻辑的库也能被其他软件组合和复用。 另外,根项目还可能包含其他文件夹,比如上面的cmake文件夹用于存放CMake配置脚本或工具函数。 每个子项目的组织格式为: subprj/CMakeLists.txt subprj/include/subprj/module.h subprj/src/module.cpp 根项目 CMakeLists.txt 在根项目中的 CMakeLists.txt 中,我们进行基本的 C++ 版本设置等选项,并使用 project 命令初始化项目。 之后,通过 add_subdirectory 将子项目逐一添加到根项目中,这样根项目就能够调用子项目中的 CMakeLists.txt 文件。 1 2 3 4 5 6 7 8 9 10 11 12 13 cmake_minimum_required(VERSION 3.30) set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_EXTENSIONS OFF) set(CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/cmake;${CMAKE_MODULE_PATH}") project(prj LANGUAGES CXX) include(MyFuncs) add_subdirectory(subprj1) add_subdirectory(mainprj) 子项目 CMakeLists.txt 根项目的 CMakeLists.txt 主要负责全局配置,而子项目中的 CMakeLists.txt 则只关注该子项目自身的设置,如头文件目录、需要链接的库等。 子项目中,通常会使用 add_library 或 add_executable 来生成target,并配置target的选项,如链接的库和包含的头文件等。 在链接库时,由于 PUBLIC 的传播作用,某项目链接了其他项目的库后,也可以自动包含相应的头文件。 1 2 3 4 file(GLOB_RECURSE srcs CONFIGURE_DEPENDS src/*.cpp include/*.h) add_executable(mainprj ${srcs}) target_include_directories(mainprj PUBLIC include) target_link_libraries(mainprj PUBLIC subprj1) 在上面的例子中,mainprj 是一个可执行子项目,它通过 add_executable 生成可执行文件,并链接了其他子项目 subprj1。 在子项目中,可以使用GLOB_RECRUSE来获取文件夹中所有的.h文件和.cpp文件,只是编译的话只需要.cpp文件,将.h也写出可以使头文件也被纳入IDE的项目资源浏览器,比如在头文件中引用头文件也可以使用<>写法搜索得到。 在上面的例子中,mainprj 是一个可执行子项目,它通过 add_executable 生成可执行文件,并链接了其他子项目 subprj1。 在子项目中,可以使用 GLOB_RECURSE 来获取文件夹中所有的 .cpp 文件和 .h 文件。虽然编译时只需要 .cpp 文件,但将 .h 文件也一并列出可以使头文件被纳入 IDE 的项目资源浏览器。比如在头文件中引用其他头文件时,也可以使用 <> 写法,可以直接跳转到目标头文件。 子项目头文件 子项目头文件的例子如下,每个头文件使用#pragma once,防止重复导入;之后将代码使用namespace subprj{}包裹,这样如果两个子库有相同标识符在使用时也不会出现冲突。 如果没有#pragma once,在头文件中定义了一个类,在实现文件中重复导入两次则会造成重复定义的编译错误,#pragma once可以保证一个编译单元中不会因为某个头文件出现重复定义。 1 2 3 // 如果下面的头文件没有防重复导入,则会出现错误 # include <subprj1/Animal.h> # include <subprj1/Animal.h> 而如果没有namespace,我在两个子库中都一个同名的类,如果某个文件同时导入这两个头文件,也会有重复定义的编译错误; 如果有相同的函数名,引入两个头文件,编译该单元时没有错误,但要链接时无法确定是哪一个函数实现,出现链接错误。 所以将每个子项目使用不同的namespace先进行包裹,“把自己先圈起来”,防止和未知的头文件或库发生冲突,namespace相当于延长了标识符,人为地划定模块,防止标识符冲突。 1 2 3 // 虽然是不同的头文件,但有相同的类标识符,一起复制过来的话就会重复定义,使用namespace即可隔离 # include "subprj1/example.h" # include "subprj2/example.h" 另外,如果要在头文件中写函数定义,需要使用inline或static修饰,它们可以使函数定义限制在该编译单元里,防止不同项目都引入函数定义后,链接时出现重复定义。 虽然在头文件中会有namespace,但解决不了这个问题,因为每个导入该头文件的都是原封不动的将头文件复制过来,相当于这个函数的全名(namespace::func)在多个文件里都被编译了一遍,那多个编译单元链接时,这个函数名字还是有多个意思(定义),从而造成冲突。 1 2 3 4 5 // 如果没有修饰,两个cpp编译后进行链接时,utils中的函数就会有多个定义造成冲突 // a.cpp # include <subprj1/utils.h> // b.cpp # include <subprj1/utils.h> 从上面可以总结的是:在同一个编译单元中,可以有同名的声明(函数声明、类声明、变量声明),但定义都只能有一个(多次导入同一头文件造成的类冲突;导入不同头文件造成的类冲突)。 在多个编译单元中,依旧可以有同名的声明,复制于同一个头文件的不同编译单元的类定义是可行的(每个编译单元中会有自己的类副本),但函数定义和变量定义不行,但函数和变量的定义只能有一个副本(使用static和inline)。 一个典型的头文件如下: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 // prj/subprj1/include/subprj1/Animal.h #pragma once namespace subprj1 { struct Animal { virtual void speak() const = 0; virtual ~Animal() = default; }; struct Dog final : Animal { void speak() const override; }; struct Cat final : Animal { void speak() const override; }; } 子项目源文件 一般源文件和相应的头文件成对出现,在源文件中,include相应的头文件,并在namespace中进行头文件的实现。 1 2 3 4 5 6 7 8 9 10 11 12 13 // prj/subprj1/src/Animal.cpp # include <subprj1/Animal.h> # include <iostream> namespace subprj1 { void Cat::speak() const { std::cout << "Cat::speak" << std::endl; } void Dog::speak() const { std::cout << "Dog::speak" << std::endl; } } 每个cpp文件是一个编译单元,一般在写新功能时,会新建一对头文件和源文件,视为一个模块。 上面提到的namespace可以为每个模块搞成一块命名空间,但一般将每个子项目作为分隔不同命名空间的尺度就可以,项目内需要人为的保证每个小模块不会发生重复定义。 如果一个模块的头文件中仅仅声明了其他模块中的类,而没有直接使用或解引用该类的成员(例如调用成员函数或访问成员变量),那么头文件中不需要包含该类对应的头文件,而只需提供一个前向声明。例如,可以使用 struct ClassName; 或 class ClassName; 来声明类。只有在实际需要使用该类成员的实现文件(如 .cpp 文件)中,才需要包含完整的头文件。 1 2 3 4 5 6 7 8 9 10 // Animal类在其他头文件定义,但该头文件中无需引用 // 我们只需要声明一下Animal是一个`struct`而不是一个函数之类的,因为此处并没有解引用Animal类 #pragma once namespace subprj1 { struct Animal; struct Another { void use(Animal *a) const; }; } cmake/ 文件夹 与 C/C++ 中的 #include 类似,CMake 也有一个 include 命令。使用 include(XXX) 时,CMake 会在 CMAKE_MODULE_PATH 列表中的所有路径下查找名为 XXX.cmake 的文件。 通过这种方式,可以将一些常用的函数、宏或变量写在独立的 XXX.cmake 文件中,存放在项目的 cmake/ 文件夹中。然后在需要使用这些功能的地方,通过 include 引入相应的 .cmake 文件,从而实现代码的复用和模块化管理。 前面的根项目CMakeLists.txt中,以下部分就是设置CMAKE_MODULE_PATH以及include相应的.cmake。 1 2 set(CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/cmake;${CMAKE_MODULE_PATH}") include(MyFuncs) 比如说在.cmake文件中写一些常用的函数或宏等: 1 2 3 4 5 6 7 8 9 10 11 # 用法:my_add_target(prj EXECUTABLE) # 通过该宏可以直接简化子项目生成target的CMake代码 macro (my_add_target name type) file(GLOB_RECURSE srcs CONFIGURE_DEPENDS src/*.cpp src/*.h) if ("${type}" MATCHES "EXECUTABLE") add_executable(${name} ${srcs}) else() add_library(${name} ${type} ${srcs}) endif() target_include_directories(${name} PUBLIC include) endmacro() macro 和 function: **macro**:相当于将代码直接粘贴到调用者的位置。 **function**:创建了一个闭包,它优先访问定义者的作用域。 include 和 add_subdirectory **include**:相当于将代码直接粘贴到调用者的作用域中。 **add_subdirectory**:会在子目录中创建一个新的作用域。 可以类比 C++ 中的#define和函数。 参考: 小彭老师的并行课 CMake Tutorial FetchContent

2024/12/6
articleCard.readMore

vscode插件配置

VSCode 本身只是一个编辑器,但许多丰富的插件拓展使它可以进行许多语言的开发。不过插件一般都需要个性化配置,以前对于插件的使用都是网页直接搜他人写的教程,没有真的自己去捣鼓如何用好一个插件,本文以 clangd 插件为例,来说明面对一个插件应当如何去使用它。 VSCode 设置界面 VSCode 的插件配置页面位于其设置界面,这里不仅包含了编辑器和窗口等常规设置项,还涵盖了各种插件的个性化设置。用户可以通过命令面板访问设置文件,直接编辑设置文件的方式进行配置。 设置文件分为 User 和 Workspace 两种作用域,其中,前者位于用户目录(~/.config/Code/User/settings.json)下,后者位于该工作目录即当前打开的文件夹或工作区({workspace}/.vscode/settings.json)中。 在进行个性化设置时,用户可以通过命令面板打开默认设置进行对比。 配置插件 以clangd为例,这是一个由llvm项目推出的C++语言服务器,它通过LSP(Language Server Protocol)协议向编辑器提供语法补全、错误检测、跳转、格式化等功能。 用户可以直接在 VSCode 的应用商店下载 clangd 插件。安装后,插件会提示用户需要安装 clangd 包。这是因为 VSCode 插件通常是基于电脑上已有的软件包进行适配,使其能够在 VSCode 中发挥作用。软件包的路径也是使用插件时需要填写的配置之一。 如果错过了插件安装后的提示,用户可以通过命令面板来下载clangd,如下图,除了新的可配置项,下载完一个插件后也会产生很多与该插件相关的命令项。 在插件的信息页面中,用户可以在FEATURES栏找到有关该插件的特征信息,包括命令与可配置项的信息。 通过这个页面,用户可以了解到clangd的各种配置项的描述与默认值,进而进行配置。另一种途径是找到该插件的开源项目,其中package.json文件中的contributes.configuration部分包含了插件配置项的定义。 注意到上图中的 clangd.arguments 配置项,该数组用来作为 clangd 服务运行的参数,想要在该地方填写,则需要学习 clangd 本身有哪些运行参数,可以通过运行 clangd --help 或 clangd --help-list-hidden 命令来查看所有可用的命令行参数,包括默认值;或者直接前往 clangd 官网学习。配置插件时为了填写软件包相关的参数,需要脱离 VSCode 插件本身,前往软件包的领域去寻找。 上图中的 clangd.fallbackFlags 配置项,则是用于指定当没有找到编译数据库(compile_commands.json)时,用于解析文件的额外 clang 编译标志。compile_commands.json 文件一般会包含项目文件之间关联信息,我们也可以通过该配置项手动声明一些标志(比如 -Ixxx 指定头文件搜索路径)。 对于在 VSCode 中配置使用 clangd,我们会涉及许多文件,其中既有与插件相关的,又有与 clangd 本身相关的: 插件配置文件:settings.json (全局用户和工作区) clangd 配置文件:~/.config/clangd/config.yaml (user configuration)和 .clangd(project configuration)。 compile_commands.json … 不同的插件配置时会有不同的配置项,但关键在于如何找到配置项、理解配置项,并熟悉整个配置流程。 备份 登录账号后,可以同步备份 VSCode 上的个性化配置,可以在新设备上无痛迁移之前的插件以及相关配置的信息。 参考: clangd 官网

2024/11/21
articleCard.readMore

MSVC,GCC,Clang——不同C/C++编译器对比

在线的 C/CPP 编译器:Compiler Explorer (godbolt.org) 20 世纪 50 年代,计算机程序主要使用汇编语言开发,这对开发者来说太难了。IBM 也希望销售更多计算机让人们编程,John Backus 针对汇编语言的缺点着手研究开发 Fortran 语言,科学家们希望利用高级语言编写程序,但同时提供接近手动调整的机器代码的性能,在 1957 年,首个编译器 IBM Fortran 诞生了。 MSVC MSVC(Microsoft Visual C++)是微软公司的 C++开发工具,具有集成开发环境,可提供编辑 C 语言,C++以及 C++/CLI 等编程语言。 VC++集成了便利的调试工具,特别是集成了微软 Windows 操作系统应用程序接口(Windows API)、三维动画 DirectX API,微软. NET 框架。 GCC GCC(GNU Compiler Collection)是由 GNU 项目开发的一套优化编译器,它支持多种编程语言、操作系统和计算机架构。作为自由软件,GCC 由自由软件基金会(FSF)在 GNU 通用公共许可证(GNU GPL)下分发。 GCC 最初名为 GNU C 编译器(GNU C Compiler),因为它最初只支持 C 语言。然而,不久之后,GCC 就扩展了对 C++ 的支持,并逐渐增加了对 Fortran、Pascal、Objective-C、Java、Ada、Go 等多种语言的支持。 GCC 不仅是 GNU 操作系统的官方编译器,也是许多类 UNIX 系统和 Linux 发行版的标准编译器。在 GCC 出现之后,BSD 家族的大部分操作系统也开始使用 GCC。不过,一些系统如 FreeBSD、OpenBSD 和 Apple 的 macOS 已经转而使用 Clang 编译器。 MinGW(Minimalist GNU for Windows)是将 GCC 编译器和 GNU Binutils 移植到 Windows 32位平台的项目。它包括了一系列头文件(用于 Win 32 API)、库和可执行文件。由于最初的 MinGW 项目更新缓慢,且不支持64位环境开发,OneVision Software 于2005年开始开发 MinGW-w64。MinGW-w64 支持 GCC 所支持的大多数编程语言,包括 C、C++、Objective-C、Objective-C++、Fortran 和 Ada。 GCC 实践: 1 2 3 4 5 6 # hello.c #include <stdio.h> #define HELLO "hello\n" int main() {   printf(HELLO); } 预处理生成.i 文件。 1 gcc -E hello.c -o hello.i 1 2 3 4 5 6 # hello.i ... ... int main() {   printf("hello\n"); } 将预处理的文件转换为特定的汇编文件。 1 gcc -S hello.i -o hello.s 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 .file"hello.c" .text .section.rodata .LC0: .string"hello" .text .globlmain .typemain, @function main: .LFB0: .cfi_startproc endbr64 pushq%rbp .cfi_def_cfa_offset 16 .cfi_offset 6, -16 movq%rsp, %rbp .cfi_def_cfa_register 6 leaq.LC0(%rip), %rax movq%rax, %rdi callputs@PLT movl$0, %eax popq%rbp .cfi_def_cfa 7, 8 ret .cfi_endproc .LFE0: .sizemain, .-main .ident"GCC: (Ubuntu 11.4.0-1ubuntu1~22.04) 11.4.0" .section.note.GNU-stack,"",@progbits .section.note.gnu.property,"a" .align 8 .long1f - 0f .long4f - 1f .long5 0: .string"GNU" 1: .align 8 .long0xc0000002 .long3f - 2f 2: .long0x3 3: .align 8 4: 将汇编代码转换为机器指令,由汇编器完成。 1 gcc -c hello.c -o hello.o 链接。 1 gcc -o hello.o -o hello Clang Clang 是一个编译器前端,支持多种编程语言,包括 C、C++、Objective-C、Objective-C++,以及支持 OpenMP、OpenCL、RenderScript、CUDA、SYCL 和 HIP 等软件框架。它可以作为 GNU 编译器集合(GCC)的替代品,并且兼容 GCC 的大多数编译标志和非官方的语言扩展。Clang 使用 LLVM 作为其编译器的后端。 LLVM 是一套编译器和工具链技术,它允许开发者为任何编程语言创建前端,为任何指令集架构创建后端。LLVM 的设计核心是一种与语言无关的中间表示(IR),这种 IR 可以看作是一种可移植的高级汇编语言,它可以通过多次转换来进行优化。LLVM 项目始于 2000 年,在伊利诺伊大学厄巴纳-香槟分校由 Vikram Adve 和 Chris Lattner 领导。最初,LLVM 是作为研究静态和动态编程语言动态编译技术的研究基础设施而开发的。 2005 年,苹果公司聘请了 Chris Lattner 并组建了一个团队,致力于将 LLVM 系统应用于苹果开发系统中的多种用途。最初,LLVM 项目计划使用 GCC 作为前端,但由于 GCC 源代码庞大且实现复杂,加之苹果的软件大量使用 Objective-C,而 GCC 对 Objective-C 的支持优先级不高,以及 GCC 的 GNU 通用公共许可证(GPL)第 3 版要求分发 GCC 扩展或修改版本的开发者必须提供源代码,这与 LLVM 的宽松许可证不符。因此,苹果公司开发了 Clang,自 Xcode 4 起,苹果的默认编译器改为了 Clang/LLVM。 Clang/LLVM 实践: 1 2 3 4 5 6 # hello.c #include <stdio.h> #define HELLO "hello\n" int main() {   printf(HELLO); } 预处理生成.i 文件。 1 clang -E -c ./hello.c -o ./hello.i 1 2 3 4 5 6 # hello.i ... ... int main() {   printf("hello\n"); } 将.i 文件转化为.bc 文件,这是 LLVM 位代码(bitcode)文件,是中间表示形式,可供进一步优化和转换。 1 clang -emit-llvm ./hello.i -c -o ./hello.bc 将.c 文件编译成 LLVM 中间表示的汇编代码文件.ll,人类可读的中间表示形式。 1 clang -emit-llvm ./hello.c -S -o ./hello.ll 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 ; ModuleID = './hello.c' source_filename = "./hello.c" target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128" target triple = "x86_64-pc-linux-gnu" @.str = private unnamed_addr constant [7 x i8] c"hello\0A\00", align 1 ; Function Attrs: noinline nounwind optnone uwtable define dso_local i32 @main() #0 { %1 = call i32 (i8*, ...) @printf(i8* noundef getelementptr inbounds ([7 x i8], [7 x i8]* @.str, i64 0, i64 0)) ret i32 0 } declare i32 @printf(i8* noundef, ...) #1 attributes #0 = { noinline nounwind optnone uwtable "frame-pointer"="all" "min-legal-vector-width"="0" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="x86-64" "target-features"="+cx8,+fxsr,+mmx,+sse,+sse2,+x87" "tune-cpu"="generic" } attributes #1 = { "frame-pointer"="all" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="x86-64" "target-features"="+cx8,+fxsr,+mmx,+sse,+sse2,+x87" "tune-cpu"="generic" } !llvm.module.flags = !{!0, !1, !2, !3, !4} !llvm.ident = !{!5} !0 = !{i32 1, !"wchar_size", i32 4} !1 = !{i32 7, !"PIC Level", i32 2} !2 = !{i32 7, !"PIE Level", i32 2} !3 = !{i32 7, !"uwtable", i32 1} !4 = !{i32 7, !"frame-pointer", i32 2} !5 = !{!"Ubuntu clang version 14.0.0-1ubuntu1.1"} 使用 llc 将中间表示文件(.bc 或 .ll)转换为目标平台的汇编代码。 1 2 3 # 这两种转换生成的汇编代码相同 llc ./hello.ll -o ./hello.s llc ./hello.bc -o ./hello.s 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 .text .file"hello.c" .globlmain # -- Begin function main .p2align4, 0x90 .typemain,@function main: # @main .cfi_startproc # %bb.0: pushq%rbp .cfi_def_cfa_offset 16 .cfi_offset %rbp, -16 movq%rsp, %rbp .cfi_def_cfa_register %rbp movabsq$.L.str, %rdi movb$0, %al callqprintf@PLT xorl%eax, %eax popq%rbp .cfi_def_cfa %rsp, 8 retq .Lfunc_end0: .sizemain, .Lfunc_end0-main .cfi_endproc # -- End function .type.L.str,@object # @.str .section.rodata.str1.1,"aMS",@progbits,1 .L.str: .asciz"hello\n" .size.L.str, 7 .ident"Ubuntu clang version 14.0.0-1ubuntu1.1" .section".note.GNU-stack","",@progbits 转变为可执行的二进制文件。 1 clang ./hello.s -o hello MSVCGCCClang/LLVM 开源协议GNU GPLApache 2.0 支持平台WindowsUNIX、Windows、MacUnix、Mac 构建工具MakeCMake 链接器ldlld 调试器GDBLLDB 编译器的「五个十年」发展史 维基百科 chenzomi12/AISystem: AISystem 主要是指AI系统,包括AI芯片、AI编译器、AI推理和训练框架等AI全栈底层技术 (github.com)

2024/10/4
articleCard.readMore

开源贡献的一些规范

一、PR流程 选择社区项目进行 Fork,开发工作应当放在自己的仓库中,个人仓库的 master 分支负责与上游仓库进行同步。 Clone 个人仓库的项目,在本地开发中,可以同时添加上游仓库源;修改代码时,从最新的 master checkout 到新的临时分支(eg:tmp-branch),在该分支上进行开发,之后 push 到远程个人仓库的一个临时分支(可以直接和本地分支名相同)上。 1 2 3 4 5 # 添加上游仓库 git remote add upstream https://github.com/xxx/xxx.git # 创建并切换到新的分支 git checkout -b tmp-branch 此时进入上游仓库,可以发现来自个人仓库临时分支的 pull request 提示,点击并填写提交信息,发起合并请求。自动化测试工具通过后,就只需等待社区人员审查,交流通过后便可以将自己的修改合进原项目。此时可以直接删除个人仓库中的临时分支。 下次修改前需要先将个人仓库的 master 分支与上游仓库进行同步。 一般在社区的首次 PR 请求,会要求贡献者签署 CLA,按照相应的提示操作即可。一些社区也会提供 playground 项目,可以在该项目中练习 PR 的流程,并完成签署 CLA。 需要注意的是,提交 PR 请求,通过后会根据 PR 的提交信息创建一个新的 commit 点,PR 中个人分支上的多条新 commit 点并不会出现在上游仓库的主分支上。 二、commit 规范 Angular 提交信息规范 需要一定的规范来约束开发人员的 commit 信息格式,其中,Angular提交信息规范是一种非常受开发人员欢迎的 commit 信息规范。 每条 commit message 都由 header、body 和 footer 组成。而 header 包括 type、scope 和 subject: 1 2 3 4 5 <type>(<scope>): <subject> <BLANK LINE> <body> <BLANK LINE> <footer> type 必须是以下值之一: feat: A new feature fix: A bug fix docs: Documentation only changes style: Changes that do not affect the meaning of the code (white-space, formatting, missing semi-colons, etc) refactor: A code change that neither fixes a bug nor adds a feature perf: A code change that improves performance test: Adding missing or correcting existing tests chore: Changes to the build process or auxiliary tools and libraries such as documentation generation subject 要求: 以动词开头,使用第一人称现在时,比如 change,而不是 changed 或 changes 不要大写首字母 不在末尾添加句号 commitizen 借助 commitizen 可以方便地规范输入 commit 信息。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 # 全局安装 npm install -g commitizen ``` 另一方面,需要安装适配器进行标准提交提示,此处选择 `cz-conventional-changelog`。 ```sh # 全局安装 npm install -g cz-conventional-changelog # 在根目录下添加文件 .czrc,内容如下 { "path": "cz-conventional-changelog" } # 本地安装 npm install --save-dev cz-conventional-changelog # 在package.json中添加配置 "config": { "commitizen": { "path": "cz-conventional-changelog" } } 此时,可以使用 cz 命令代替 git commit,进行规范化 commit 提交。 三、社区术语 缩写英文中文 PRPull request拉取请求 BTWBy the way顺便说一句 WIPWork in progress, do not merge yet进行中暂时不要合并 LGTMLooks good to me在我看来很好 PTALPlease take a look帮我看下,请别人 review 自己的 PR CCCarbon copy抄送 FYIFor your information供你参考 RFCRequest for comments我觉得这个想法很好, 我们来一起讨论下 IIRCIf I recall correctly如果我没记错 ACKAcknowledgement认可 NACK/NAKNegative acknowledgement不认可 参考: Github 常用社交用语 -腾讯云开发者社区-腾讯云 (tencent.com) Angular 提交信息规范 使用标准commit生成changelog标准化 - 简书 (jianshu.com)

2024/9/7
articleCard.readMore

Github Pages + Hexo 搭建个人博客

一、Hexo Hexo 安装和基本使用 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 # 准备工作:安装NodeJS # 全局安装Hexo npm install -g hexo-cli # 初始化Hexo项目 hexo init hexo-blog cd hexo-blog # 查看目录结构 tree -L 1 .├── _config.landscape.yml ├── _config.yml# 配置,比如之后在该文件配置github账号 ├── node_modules ├── package-lock.json ├── package.json ├── scaffolds ├── source# source/_posts 存放博客文件(Markdown格式) └── themes# 更改博客主题皮肤 # 常用Hexo命令 hexo init [folder]# 初始化 hexo new [layout] <title># 新建文章 hexo generate# 生成静态文件,根目录生成public文件夹 hexo server# 启动本地服务器,http://localhost:4000/ hexo deploy# 部署到远程 (hexo -g d 生成静态文件并部署) hexo clean# 清除缓存文件 (db.json) 和已生成的静态文件 (public) 更换主题 官网提供了多种可选主题 Themes | Hexo 选择某个主题下载压缩包,以 Fluid 主题为例,在 GitHub 中下载 Releases 包并解压,重命名为 fluid 放在 themes 目录下。 更改 _config.yml 1 2 theme: fluid # 指定主题 language: zh-CN # 指定主题显示语言 新建文章 1 2 3 4 5 6 7 8 9 10 11 # 新建 hexo new my-blog# hexo new post my-blog 指定layout # 查看变化 tree source/_posts source/_posts ├── hello-world.md └── my-blog.md # 写入内容后本地测试 hexo s 通过 new 命令可以在 source/_posts 文件夹下创建相应的 Markdown 文件。 通常需要在文章中插入图片,为了更规律的管理文章的图片,为每篇文章准备相对应的资源文件夹,将 _config.yml 文件中的 post_asset_folder 选项设为 true 后,创建新文章时自动创建一个同名资源文件夹。 为了使用 Markdown 引用图片,继续改动 _config.yml。 1 2 3 4 post_asset_folder: true marked: prependRoot: true postAsset: true 之后可以在文章中直接通过 ![](xxx.jpg) 引用对应的图片。 通过更改主题的 _config.yml 文件可以实现个性化,比如改变主页某处的文字、增加阅读量记录、增加评论功能等,可以根据主题相应的文档进行更改。可以注册 LeanCloud,使用其统计服务等功能。 二、Github Pages 在 Hexo 项目中安装 hexo-deployer-git 包。 1 npm install hexo-deployer-git --save 新建仓库,仓库名格式为 <用户名>.github.io ;在 _config.yml 中配置 GitHub 的相关信息。token 可以在 github 的 Settings/Developer Settings 中生成。 1 2 3 4 5 deploy:   type: git   repository: git@github.com:kaysonyu/kaysonyu.github.io.git   branch: master   token: xxx 部署到 github 1 hexo -g d 其他相关:Netlify,Vercel,CF Pages 参考: Hexo 中文文档 GitHub Pages + Hexo搭建个人博客网站,史上最全教程_hexo博客-CSDN博客 配置指南 | Hexo Fluid 用户手册 (fluid-dev.com)

2024/8/15
articleCard.readMore

将光猫设置为桥接

光猫:光电信号转化。 路由器:路由扩展网络,WAN 口接上层网络,LAN 口或者无线 WiFi 接扩展设备,形成一个新的局域网。 光猫设置为路由: 光猫负责光电信号转化,同时负责拨号上网,此时连接光猫的设备可直接上网,通过 DHCP 获取 ip;此时光猫一侧是外网,一侧是自身形成的局域网(eg:192.168.1.x),通过路由器可以进一步扩展网络。 光猫设置为桥接: 此时光猫只负责光电信号转化,起桥梁作用,此时连接路由器后,路由器通过拨号上网,路由器一侧直接是外网(前者路由器一侧是光猫的下属网络),另一侧是路由器下属局域网。这种设置既将拨号负担置于性能更强的路由器(光猫专注于转化工作),又简化了网络的层级关系,将外网-光猫-路由器的网络结构简化为外网-路由器。需要注意的是:仅能单个设备进行拨号,所以改为桥接后不能再将电脑直接连接光猫拨号(路由器已经拨号)。

2024/8/13
articleCard.readMore

Ubuntu安装记录

安装Ubuntu系统(包含Nvidia驱动) CPU: i7-14700K 显卡: GeForce RTX 4060 Ti 16 g U盘启动后选项如下: 失败记录: 选择Try or Install Ubuntu无法进入安装界面,一直停留在Ubuntu的加载界面; 选择Ubuntu(safe graphics)进入安装,但过程中失败,通过log发现有联网需求,之后操作选择使用有线网络; 重复几次后,突然前两种安装+是否添加nomodeset共四种方法均无法成功安装,选择清空硬盘和U盘并重制启动U盘; 安装Ubuntu后再次启动一直黑屏无反应,后来将HDMI从显卡换到主板上解决。 步骤: 由于采用双硬盘双系统的安装策略,所以先拔掉装有Windows的硬盘,防止EFI装在同一块硬盘上。 选择Ubuntu(safe graphics),按E进入配置,在quiet splash后面添加nomodeset参数,之后按F10进入Ubuntu 24.04的安装。 nouveau是通过逆向得到的非官方的Nvidia驱动,该驱动可能在一些较新的显卡上工作不正常,通过nomodeset来禁止加载该驱动,防止引导时黑屏。 安装后重启拔掉U盘,此时将HDMI换到主板相应接口,否则一直黑屏。 此处针对黑屏的参考资料中提到同样在Ubuntu引导界面进行如上屏蔽nouveau的操作后可以正常进入,但我也没有显示引导界面,完全黑屏,换到主板接口后则无需操作的正常进入系统。 进入系统后需要完全禁止nouveau,使得之后可以自动屏蔽该驱动。之后重启,可以将HDMI重新接到显卡接口。 1 2 3 4 5 6 # 编辑黑名单文件 sudo nano /etc/modprobe.d/blacklist.conf # 在末尾添加如下条目 blacklist nouveau # 使黑名单生效 sudo update-initramfs -u 安装Nvidia官方驱动之前可以先检查是否会更换内核版本,我的版本中无线网卡未成功工作,升级了Linux内核版本,但由于安装后的Nvidia驱动在切换版本后不生效,所以后来进行了卸载重装,具体留在无线网卡部分。 之后安装Nvidia官方驱动,在官网下载对应显卡的驱动程序。此时直接运行会遇到各种问题: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 # 问题 WARNING: Unable to find suitable destination to install 32-bit compatibility libraries # 解决 sudo dpkg --add-architecture i386 sudo apt update sudo apt install libc6:i386 # 问题 An incomplete installation of libglvnd was found.                   All of the essential libglvnd libraries are present, but one or more optional  components are missing. Do you want to install a full copy of libglvnd? This will overwrite any existing libglvnd libraries. # 解决 sudo apt install libglvnd-dev # 问题 WARNING: Unable to determine the path to install the libglvnd EGL vendor library config files. Check that you have pkg-config and the libglvnd development libraries installed, or specify a path with --glvnd-egl-config-path. # 解决 sudo apt install pkg-config 所以安装前先进行下载: 1 2 3 4 5 6 7 8 9 10 sudo dpkg --add-architecture i386 sudo apt update sudo apt install libc6:i386 sudo apt install build-essential libglvnd-dev pkg-config # 停止当前显示服务器 sudo telinit 3 # 运行驱动安装 sudo bash NVIDIA-Linux-x86_64-550.107.02.run # 重启 sudo reboot 驱动安装完成。 中文输入法 在 Settings 的 Region & Language 中安装汉语,应用并重启。 在Keyboard中添加拼音键入。 卸载snap(安装Chrome) 先安装Chrome(之后需要卸载snap版的Firefox),下载deb包,调用sudo dpkg -i xxx.deb安装即可。 卸载snap: 删除已安装的 Snap 软件。 1 2 3 4 5 6 7 # 多执行几次 for p in $(snap list | awk '{print $1}'); do     sudo snap remove $p done # 成功效果 snaps are installed yet. Try 'snap install hello-world'.   (删除 Snap 的 Core 文件) 1 2 3 4 5 sudo systemctl stop snapd                                           sudo systemctl disable --now snapd.socket for m in /snap/core/*; do    sudo umount $m done 删除 Snap 管理工具 1 sudo apt autoremove --purge snapd 删除 Snap 的目录 1 2 3 4 5 rm -rf ~/snap                                                       sudo rm -rf /snap sudo rm -rf /var/snap sudo rm -rf /var/lib/snapd sudo rm -rf /var/cache/snapd 禁止 apt 安装 snapd 1 2 3 4 5 sudo nano /etc/apt/preferences.d/no-snapd.pref # 文件内容 Package: snapd                                                     Pin: release a=* Pin-Priority: -10 安装Clash Clash_for_Windows资源站:https://archive.org/download/clash_for_windows_pkg 下载并解压 1 2 3 4 tar -zxvf Clash.for.Windows-0.18.5-x64-linux.tar.gz mv Clash.for.Windows-0.18.5-x64-linux clash cd Clash ./cfw 导入订阅 设置系统代理 美化主题 美化项: 图标主题:更改图标主题将更改应用程序图标的外观,最好选择支持各种应用程序的图标主题,否则可能会看到一些图标外观保持不变甚至看起来不合适。 GTK 主题:GTK 是用于构建应用程序的图形用户界面的框架。基本上,它决定了应用程序界面的外观(前提是这个应用程序是 GTK 应用程序,大部分的系统应用程序都是 GTK 应用程序)。 GNOME Shell 主题:更改 GNOME Shell 主题将更改 Shell 元素,例如顶部面板、活动概览、桌面通知等。 光标主题 环境安装: 1 2 3 4 5 6 # 安装tweak工具,添加名为Tweak的应用程序,可以用来更改主题、图标等 sudo apt install gnome-tweaks # 可以在chrome中下载gnome-shell扩展,https://extensions.gnome.org/ sudo apt install chrome-gnome-shell # 添加名为Extensions的应用程序,可以进行gnome-shell扩展的管理与配置 sudo apt install gnome-shell-extensions 在扩展中,启用User Themes后可以在Tweak中进行Shell主题的更改,启动Dash to Dock后可以灵活美观的控制Dock栏。 主题下载地址:https://www.opendesktop.org/ 存放位置: ~/.themes:GTK 主题,GNOME Shell 主题 ~/.icons:图标主题,光标主题 终端 安装Zsh并使用其作为Shell 安装Zim并借助其安装p10k主题以及其他插件,见Shell文章 更换Linux内核 板载无线网卡无法使用,由lshw命令可以得到无线网卡品牌为WCN785x Wi-Fi 7(802.11be) 320MHz 2x2 [FastConnect 7800],对比下面的以太网,该无线网卡没有合适的驱动程序。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 # lshw lshw -C network WARNING: you should run this program as super-user. *-network description: Network controller product: WCN785x Wi-Fi 7(802.11be) 320MHz 2x2 [FastConnect 7800] vendor: Qualcomm Technologies, Inc physical id: 0 bus info: pci@0000:06:00.0 version: 01 width: 64 bits clock: 33MHz capabilities: bus_master cap_list configuration: driver=ath12k_pci latency=0 resources: irq:204 memory:82200000-823fffff *-network description: Ethernet interface product: Ethernet Controller I225-V vendor: Intel Corporation physical id: 0 bus info: pci@0000:07:00.0 logical name: enp7s0 version: 03 serial: d8:43:ae:79:a2:80 size: 100Mbit/s capacity: 1Gbit/s width: 32 bits clock: 33MHz capabilities: bus_master cap_list ethernet physical tp 10bt 10bt-fd 100bt 100bt-fd 1000bt-fd autonegotiation configuration: autonegotiation=on broadcast=yes driver=igc driverversion=6.8.0-39-generic duplex=full firmware=1073:8754 ip=192.168.1.2 latency=0 link=yes multicast=yes port=twisted pair speed=100Mbit/s resources: irq:19 memory:82e00000-82efffff memory:82f00000-82f03fff WARNING: output may be incomplete or inaccurate, you should run this program as super-user. # dmesg sudo dmesg ... [ 5.453515] ath12k_pci 0000:06:00.0: chip_id 0x2 chip_family 0x4 board_id 0xff soc_id 0x40170200 [ 5.453526] ath12k_pci 0000:06:00.0: fw_version 0x100301e1 fw_build_timestamp 2023-12-06 04:05 fw_build_id QC_IMAGE_VERSION_STRING=WLAN.HMT.1.0.c5-00481-QCAHMTSWPL_V1.0_V2.0_SILICONZ-3 [ 5.453650] ath12k_pci 0000:06:00.0: qmi failed to load regdb bin: [ 5.453653] ath12k_pci 0000:06:00.0: qmi failed to load regdb file:-2 ... 通过查阅相关问题,得知可以通过更换最新稳定版的Linux内核(提供了相应的Wi-Fi 7的驱动)来解决。 查看当前内核版本: 1 2 uname -r                                                           6.8.0-39-generic 安装最新的稳定版内核: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 # 下载最新的稳定版内核 wget -c https://kernel.ubuntu.com/mainline/v6.10.2/amd64/linux-headers-6.10.2-061002_6.10.2-061002.202407271100_all.deb wget -c https://kernel.ubuntu.com/mainline/v6.10.2/amd64/linux-headers-6.10.2-061002-generic_6.10.2-061002.202407271100_amd64.deb wget -c https://kernel.ubuntu.com/mainline/v6.10.2/amd64/linux-image-unsigned-6.10.2-061002-generic_6.10.2-061002.202407271100_amd64.deb wget -c https://kernel.ubuntu.com/mainline/v6.10.2/amd64/linux-modules-6.10.2-061002-generic_6.10.2-061002.202407271100_amd64.deb # 安装 sudo dpkg -i *.deb # 重启 sudo reboot # 验证 uname -r 6.10.2-061002-generic 更换到6.10.2版本的内核后,可以搜索到无线网络,表明驱动正常工作。但是,nvidia-smi失败,更换Linux内核版本会导致Nvidia驱动发生问题,所以可以先选择合适的内核版本。 1 2 3 4 5 6 nmcli device status DEVICE TYPE STATE CONNECTION enp7s0 ethernet connected Wired connection 1 wlp6s0 wifi connected TP-LINK_9B19 lo loopback connected (externally) lo p2p-dev-wlp6s0 wifi-p2p disconnected -- 从当前存在的内核版本间切换: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 # 列出当前所有内核版本,每个menuentry项是一个内核 sudo grep menuentry /boot/grub/grub.cfg if [ x"${feature_menuentry_id}" = xy ]; then menuentry_id_option="--id" menuentry_id_option="" export menuentry_id_option menuentry 'Ubuntu' --class ubuntu --class gnu-linux --class gnu --class os $menuentry_id_option 'gnulinux-simple-dbc01096-0009-4726-8fa2-076494d33602' { submenu 'Advanced options for Ubuntu' $menuentry_id_option 'gnulinux-advanced-dbc01096-0009-4726-8fa2-076494d33602' { menuentry 'Ubuntu, with Linux 6.10.2-061002-generic' --class ubuntu --class gnu-linux --class gnu --class os $menuentry_id_option 'gnulinux-6.10.2-061002-generic-advanced-dbc01096-0009-4726-8fa2-076494d33602' { menuentry 'Ubuntu, with Linux 6.10.2-061002-generic (recovery mode)' --class ubuntu --class gnu-linux --class gnu --class os $menuentry_id_option 'gnulinux-6.10.2-061002-generic-recovery-dbc01096-0009-4726-8fa2-076494d33602' { menuentry 'Ubuntu, with Linux 6.8.0-39-generic' --class ubuntu --class gnu-linux --class gnu --class os $menuentry_id_option 'gnulinux-6.8.0-39-generic-advanced-dbc01096-0009-4726-8fa2-076494d33602' { menuentry 'Ubuntu, with Linux 6.8.0-39-generic (recovery mode)' --class ubuntu --class gnu-linux --class gnu --class os $menuentry_id_option 'gnulinux-6.8.0-39-generic-recovery-dbc01096-0009-4726-8fa2-076494d33602' { menuentry "Memory test (memtest86+x64.efi)" --class memtest $menuentry_id_option 'memtest86+' { menuentry 'Memory test (memtest86+x64.efi, serial console)' --class memtest $menuentry_id_option 'memtest86+-serial' { menuentry 'UEFI Firmware Settings' $menuentry_id_option 'uefi-firmware' { # 更改默认启动内核,将文件中的`GRUB_DEFAULT=0`改为相应的`GRUB_DEFAULT="1> ?"` sudo nano /etc/default/grub GRUB_DEFAULT="1> 2" # 改回`Ubuntu, with Linux 6.8.0-39-generic` 参考: 如何在Ubuntu 20.04 Linux上安装NVIDIA驱动程序 NVIDIA显卡的Ubuntu驱动程序安装方法 删除 Snap 的方法 Ubuntu 配置clash的四种方式 Ubuntu 23 安装Gnome主题 GNOME 桌面美化 How to Install Themes in Ubuntu Linux Issue with Wireless Network Connection on Ubuntu 24.04 LTS How to upgrade Ubuntu to 24.04 and kernel to latest version

2024/8/6
articleCard.readMore

Ubuntu包管理

APT APT 是基于 Ubuntu/Debian 的 Linux 发行版上的默认包管理器。 常用命令: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 # 列出所有可更新的软件清单命令 sudo apt update # 升级软件包 sudo apt upgrade # 列出可更新的软件包及版本信息 apt list --upgradable # 列出所有已安装的包 apt list --installed # 列出所有已安装的包的版本信息 apt list --all-versions # 安装指定的软件命令 sudo apt install <package_name> # 更新指定的软件命令 sudo apt update <package_name> # 显示软件包具体信息,例如:版本号,安装大小,依赖关系等等 sudo apt show <package_name> # 删除软件包命令 sudo apt remove <package_name> # 清理不再使用的依赖和库文件 sudo apt autoremove # 移除软件包及配置文件 sudo apt purge <package_name> # 查找软件包命令 sudo apt search <keyword> 软件包存储库 Ubuntu 软件包存储库信息存储在 /etc/apt/sources.list 文件中。第三方 PPA 和其他存储库作为 .list 文件存储在 /etc/apt/sources.list.d/ 目录中。 下面为 Ubuntu22.04LTS 的 /etc/apt/sources.list 文件: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 # See http://help.ubuntu.com/community/UpgradeNotes for how to upgrade to # newer versions of the distribution. deb http://archive.ubuntu.com/ubuntu/ jammy main restricted # deb-src http://archive.ubuntu.com/ubuntu/ jammy main restricted ## Major bug fix updates produced after the final release of the ## distribution. deb http://archive.ubuntu.com/ubuntu/ jammy-updates main restricted # deb-src http://archive.ubuntu.com/ubuntu/ jammy-updates main restricted ## N.B. software from this repository is ENTIRELY UNSUPPORTED by the Ubuntu ## team. Also, please note that software in universe WILL NOT receive any ## review or updates from the Ubuntu security team. deb http://archive.ubuntu.com/ubuntu/ jammy universe # deb-src http://archive.ubuntu.com/ubuntu/ jammy universe deb http://archive.ubuntu.com/ubuntu/ jammy-updates universe # deb-src http://archive.ubuntu.com/ubuntu/ jammy-updates universe ## N.B. software from this repository is ENTIRELY UNSUPPORTED by the Ubuntu ## team, and may not be under a free licence. Please satisfy yourself as to ## your rights to use the software. Also, please note that software in ## multiverse WILL NOT receive any review or updates from the Ubuntu ## security team. deb http://archive.ubuntu.com/ubuntu/ jammy multiverse # deb-src http://archive.ubuntu.com/ubuntu/ jammy multiverse deb http://archive.ubuntu.com/ubuntu/ jammy-updates multiverse # deb-src http://archive.ubuntu.com/ubuntu/ jammy-updates multiverse ## N.B. software from this repository may not have been tested as ## extensively as that contained in the main release, although it includes ## newer versions of some applications which may provide useful features. ## Also, please note that software in backports WILL NOT receive any review ## or updates from the Ubuntu security team. deb http://archive.ubuntu.com/ubuntu/ jammy-backports main restricted universe multiverse # deb-src http://archive.ubuntu.com/ubuntu/ jammy-backports main restricted universe multiverse deb http://security.ubuntu.com/ubuntu/ jammy-security main restricted # deb-src http://security.ubuntu.com/ubuntu/ jammy-security main restricted deb http://security.ubuntu.com/ubuntu/ jammy-security universe # deb-src http://security.ubuntu.com/ubuntu/ jammy-security universe deb http://security.ubuntu.com/ubuntu/ jammy-security multiverse # deb-src http://security.ubuntu.com/ubuntu/ jammy-security multiverse deb-src deb deb-src: 包含软件源代码、版本修改说明、构建指令以及编译工具等。允许用户下载软件包的源代码,从而可以自己编译软件。这通常用于开发者或那些需要定制软件包或需要最新开发版本的用户。 deb: 包含可执行文件、库文件、配置文件、man/info页面、版权声明和其它文档。提供了预先编译好的软件包,用户可以直接安装而无需自己编译。 jammy: Ubuntu22.04 代号 Ubuntu 18.04 LTS bionic Ubuntu 20.04 LTS focal Ubuntu 22.04 LTS jammy Ubuntu 24.04 LTS noble main restricted universe multiverse 软件包存储库 main: 保留了Ubuntu支持的自由和开源软件。 restricted: 保留专有驱动程序(即 NVIDIA)。 universe: 保留了社区维护的免费开源软件。 multiverse: 保留有版权限制或法律问题的软件。 jammy jammy-security jammy-backports jammy-updates jammy-proposed 不同分支 基础:由于ubuntu是每6个月发行一个新版,当发行后,所有软件包的版本在这六个月内将保持不变,即使是有新版都不更新。除开重要的安全补丁外,所有新功能和非安全性补丁将不会提供给用户更新。   security:仅修复漏洞,并且尽可能少的改变软件包的行为,低风险。 backports:backports 的团队则认为最好的更新策略是 security 策略加上新版本的软件(包括候选版本的),但不会由Ubuntu security team审查和更新。 updates:包含了针对特定版本的更新,这些更新是在原始版本发布之后提供的,可能包括安全修复、bug修复和一些较小的功能改进。   proposed:updates类的测试部分,仅建议提供测试和反馈的人进行安装。 dpkg dpkg是Debian的一个底层包管理工具,主要用于对已下载到本地和已安装的软件包进行管理。 常用命令: 1 2 3 4 5 dpkg -i <package> # 安装一个在本地文件系统上存在的Debian软件包 dpkg -r <package> # 移除一个已经安装的软件包 dpkg -P <package> # 移除已安装软件包及配置文件 dpkg -L <package> # 列出安装的软件包清单 dpkg -s <package> # 显出软件包的安装状态 参考: 源中的 backports proposed security updates 的意思(已解决) - Ubuntu中文论坛 Linux apt 命令 | 菜鸟教程 (runoob.com) Ubuntu 软件包管理工具 —— dkpg、apt(dpkg常用指令、apt 软件源的配置)-CSDN博客

2024/8/1
articleCard.readMore

Shell

Shell分类 Windows Windows has two command-line shells: the Command shell (cmd) PowerShell cmd可以运行win命令;PowerShell可以运行win命令和cmdlet命令。 cmd脚本语言.bat;PowerShell脚本语言.ps1 Linux sh, csh, tcsh, bash, zsh, fish… Bash Shell 1989 年首次发布,由 Brain Fox 为 GNU 项目编写的,目的是作为 Bourne Shell (sh) 的免费软件替代品,是大多数 Linux 发行版的默认 Shell 环境。 用户配置设置在 .bashrc 中。 Z Shell 1990 年由 Paul Falstad 发布,它具有 Bash、Korn Shell 和 C Shell 共有的一些功能,macOS 默认使用 Zsh Shell。 比 Bash 可配置度更高,有围绕 Z Shell 构建的框架(Oh My Zsh)。 用户配置设置在 .zshrc 中。 Fish Shell 2005 年由 Axel Liljencrantz 创建,不符合 POSIX shell 标准。 由于默认情况下 Fish 已经预置了很多配置,因此它被认为比 Zsh 等其他 sh 选项更适合初学者。 自定义工具 查看/切换shell 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 # 查看可用shell cat /etc/shells                           # /etc/shells: valid login shells /bin/sh /bin/bash /usr/bin/bash /bin/rbash /usr/bin/rbash /usr/bin/sh /bin/dash /usr/bin/dash /usr/bin/tmux /usr/bin/screen /bin/zsh /usr/bin/zsh # 查看当前shell echo $SHELL                                                             # 可以通过包管理器安装zsh # 切换成zsh chsh -s $(which zsh) 一些主题中的部件需要特殊字体,下载使用使用Nerd字体。 Window:官网下载MesloLG Nerd Font,将所有的TrueType字体文件右键进行安装。 MacOS/Linux:脚本安装。 1 2 3 git clone https://github.com/ryanoasis/nerd-fonts.git --depth 1 cd nerd-fonts ./install.sh Oh my zsh 适用于zsh。 1 2 3 4 # 安装命令 sh -c "$(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)" # 卸载 uninstall_oh_my_zsh 安装后~/.zshrc会改变,从而启动oh-my-zsh,此时终端如下: 之后可以下载主题和插件,一方面要将下载保存到$ZSH_CUSTOM(/.oh-my-zsh/custom),另一方面要在`/.zshrc`进行相应设置以生效。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 # 主题$ZSH_CUSTOM/themes,插件$ZSH_CUSTOM/plugins # powerlevel10k主题   git clone https://github.com/romkatv/powerlevel10k.git $ZSH_CUSTOM/themes/powerlevel10k   # zsh-autosuggestions自动提示插件   git clone https://github.com/zsh-users/zsh-autosuggestions $ZSH_CUSTOM/plugins/zsh-autosuggestions   # zsh-syntax-highlighting语法高亮插件   git clone https://github.com/zsh-users/zsh-syntax-highlighting.git $ZSH_CUSTOM/plugins/zsh-syntax-highlighting # 对于p10k,需要进行自定义配置 p10k configure # ~/.zshrc # 修改主题   ZSH_THEME="powerlevel10k/powerlevel10k"   # 启用插件   plugins=(     git     zsh-autosuggestions     zsh-syntax-highlighting   ) # 更改配置后重载 omz reload 效果展示: Zim Zim 启动速度更快,相比 oh-my-zsh 更加轻量级,适用于zsh。 1 2 # 安装命令 curl -fsSL https://raw.githubusercontent.com/zimfw/install/master/install.zsh | zsh 安装后~/.zshrc会改变,而Zim的配置文件为~/.zimrc,在该文件中列出需要使用的插件(包括主题),比如: 1 2 zmodule romkatv/powerlevel10k zmodule completion 之后运行zimfw install来安装所需插件,与oh-my-zsh类似,同样需要配置p10k主题。 效果展示: Oh my posh 可以用于多种shell。 可以在Windows中美化PowerShell的样式。 1 2 # 安装命令 winget upgrade JanDeDobbeleer.OhMyPosh -s winget Windows中自带蓝色的Windows PowerShell,可以在Microsoft Store中下载黑色的PowerShell,本文使用黑色的PowerShell作为终端的默认shell。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 # 查看使用的shell oh-my-posh get shell # PowerShell配置脚本文件位置(类似~/.zshrc) (base) PS C:\Users\kangy> $PROFILE C:\Users\kangy\Documents\WindowsPowerShell\Microsoft.PowerShell_profile.ps1 # 编辑PowerShell配置文件脚本 notepad $PROFILE # 若文件不存在则创建 New-Item -Path $PROFILE -Type File -Force # 在脚本中添加如下启动oh-my-posh并使主题生效 & ([ScriptBlock]::Create((oh-my-posh init pwsh --config "$env:POSH_THEMES_PATH\jandedobbeleer.omp.json" --print) -join "`n")) # 重载shell . $PROFILE 效果展示:

2024/7/27
articleCard.readMore