【C 陷阱与缺陷】(四)连接

2023-05-16

码字不易,对你有帮助 点赞/转发/关注 支持一下作者

微信搜公众号:不会编程的程序圆

看更多干货,获取第一时间更新

代码,练习上传至:

https://github.com/hairrrrr/C-CrashCourse

一 链接

0. 什么是连接器

C 语言的一个重要思想就是分别编译(separate compilation),即若干个源程序可以在不同的时候单独进行编译,然后在恰当的时候整合在一起。但是,连接器一般是与 C 编译器分离的,它不可能了解 C 语言的诸多细节。

**连接器的工作原理:**连接器的输入是一组目标模块和库文件。连接器的输出是一个载入摸块。连接器读入目标模块和库文件同时生皮载入模块。对每个目标核块中的每个外部对象,链接器都要检查载入模块。查看是否有同名的外部对象。如果没有,连接器就将该外部对象添加到入模块中,如果有,连接器就要开始处理命名冲突。

**外部对象:**程序中的每个函数和每个外部变量,如果没有被声明为 static,就都是一个外部对象。

除了外部对象之外,目标模块中还可能包括了对其他模块中的外部对象的引用。例如一个调用了函数 printf 的 C 程序所生成的目标模块,就包括了一个对库函数 printf 的引用。可以推测得出,该引用指向的是一个位于某个库文件中的外部对象。在连接器生成载入模块的过程中,它必须同时记录这些外部对象的应用。当连接器读入一个目标模块时,它必须解析出这个目标模块中定义的所有外部对象的引用,并标记这些外部对象不再是未定义的。

1. 声明与定义

声明语句:

int a;

如果其位置出现在所有函数体之外,那么它就被称为外部对象 a 的定义。这个语句说明了 a 是一个外部整型变量,同时为 a 分配内存空间。它的初始值默认为 0 。

下面的声明语句:

int a = 7;

不仅为 a 分配了内存空间,而且说明了在该内存中应该存储的值。

下面的声明语句:

extern int a;

并不是对 a 的定义。这个语句仍然说明了 a 是一个外部整型变量,但是 a 的存储空间是在程序的其他地方分配的。从连接器的角度来看,上面的声明是对 a 的引用,而不是定义。

void srand(int n){
    extern int random_seed;
    random_seed = n;
}

每个外部对象都必须在某个地方进行定义。因此,如果程序中包括了语句:

extern int a;

那么,这个程序就必须在别的某个地方包括语句:

int a;

这两个语句既可以是在同一个源文件中,也可以位于程序的不同源文件中。

严格的规则是,每个外部变量都只能被定义一次

2. 命名冲突与 static 修饰符

两个具有相同名称的外部对象实际上代表的是同一个对象,即使编程者的本意并非如此,但系统却会如此处理。因此,如果在两个不同的源文件中都包括了定义:

int a;

那么,它或者表示程序错误(如果连接器禁止外部变量重复定义的话),或者在两个源文件中共享 a 的同一个实例(无论两个源文件中的外部变量 a 是否应该共享)。

即使其中 a 的一个定义是出现在系统提供的库文件中,也仍然进行同样的处理。当然,一个设计良好的函数库不至于定义 a 作外部名称。但是,要了解函数库中定义的所有外部对象名称却也并非易事。类似于read 和 write 这样的名称不难猜到,但其他的名称就没有这么容易了。

static 修饰符是一个能够减少此类命名冲突的有用工具。例如,以下声明语句:

static int a;

其含义与下面的语句相同

int a;

只不过,a 的作用域限制在一个源文件内,对于其他源文件,a是不可见的。因此,如果若干个函数需要共享一组外部对象,可以将这些函数放到一个源文件中,把它们需要用到的对象也都在同一一个源文件中以 static 修饰符声明。

static修饰符不仅适用于变量,也适用于函数。如果函数 f 需要调用另一个函数 g ,而且只有函数 f 需要调用函数 g ,我们可以把函数 g 和 f 放到同一个源文件中,并声明函数 g 为 static:

static int g(int x){
    // 函数体
}
int f(){
    // 其他内容
    b = g(a);
}

我们可以在多个源文件中定义同名的函数 g,只要所有的函数 g 都被定义为 static,或者仅仅只有其中一个函数 g 不是static 。因此,为了避免可能出现的命名冲突,如果一个函数仅仅被同一个源文件中的其他函数调用,我们就应该声明该函数为 static。

3. 形参,实参与返回值

如果任何一个函数在调用它的每个文件中,都在第一次被调用之前进行了声明或定义,那么就不会有任何与返回类型相关的麻烦。

比如一个调用 square 函数的程序:

main(){
    printf("%g\n", square(3.0));
}

要使这个程序能够运行,函数 square 必须要么在 main 函数之前进行定义:

double
square(double x){
    return x * x;
}

main(){
    printf("%g\n", square(3.0));
}

要么在 main 函数前进行声明:

double square(double);

main(){
    printf("%g\n", square(3.0));
}

double
square(double x){
    return x * x;
}

如果一个函数在被定义或声明之前被调用,那么它的返回类型就默认为整型。比如将上面的 main 函数放到一个独立的源文件中:

main(){
    printf("%g\n", square(3.0));
}

main 函数假定函数 square 返回类型为整型,而函数 square 返回类型实际上是双精度类型,当他与 square 函数连接时就会得出错误的结果。

如果我们需要在两个不同的源文件中分别定义函数 main 和函数 square ,那么应该在调用 square 函数的文件中声明 square 函数。比如:

double square(double);

main(){
    printf("%g\n", square(3.0));
}

ANSI C 允许程序员在声明时指定函数的参数类型(省略也是可以的,但是在函数定义时是不能省略参数类型的说明)。

double square(double);

像下面这样声明也是可以的:

double square();

默认实参提升

  • float 类型参数会转换为 double 类型
  • char 类型,short 类型参数会转换为 int 类型

对于声明:

int isvowel(char);

如果在使用 isvowel 函数前没有这样声明,调用者将把传递给 isvowel 函数实参自动转换为 int 类型。

main(){
    double s;
    s = sqrt(2);
    printf("%g\n", s);
}

上面的程序不能正常运行,原因有两个:

  • sqrt 函数本该接受一个双精度的值作为实参,而实际上被传递了一个整型
  • sqrt 函数的返回类型是双精度类型,但却没有这样声明。

一种更正方式是:

double sqrt(double);

main(){
    double s;
    s = sqrt(2.0);
    printf("%g\n", s);
}

当然,最好的更正的方式是这样:

#include<math.h>

main(){
    double s;
    s = sqrt(2.0);
    printf("%g\n", s);
}

上面 sqrt 的实参已经修改为 2.0,然而即使仍然写成 2,在符合 ANSI C 的编译器上,这个程序也能确保实参会被转换为恰当的类型。

因为函数 printf 和函数 scanf 在不同情形下可以接受不同类型的参数,所以它们特别容易出错。这里有个值得注意的例子:

#include<stdio.h>

int main() {

	int i;
	char c;
	for (i = 0; i < 5; i++) {
		scanf("%d", &c);
		printf("%d ", i);
	}
	printf("\n");

	return 0;
}

表面上,这个程序从标准输入设备读入 5 个数,在标准输出设备上写 5 个数:0 1 2 3 4

实际上,这个程序并不是一定得到上面的结果。例如,在某个编译器上,它的输出是:0 0 0 0 0 1 2 3 4

为什么呢?问题的关键在于,这里 c 被声明为 char 类型,而不是 int 类型。当程序要求 scanf 读入一个整数,应该传递给它一个指向整数的指针。而程序中scanf函数得到的却是一一个指向字符的指针,scanf 函数并不能分辨这种情况,它只是将这个指向字符的指针作为指向整数的指针而接受,并且在指针指向的位置存储一个整数。因为整数所占的存储空间要大于字符所占的存储空间,所以字符 c 附近的内存将被覆盖。

字符 c 附近的内存中存储的内容是由编译器决定的,本例中它存放的是整数 i 的低端部分。因此,每次读入一个数值到 c 时,都会将i的低端部分覆盖为 0 ,而 i 的高端部分本来就是 0 ,相当于 i 每次被重新设置为 0, 循环将一直进行。当到达文件的结束位置后,scanf 函数不再试图读入新的数值到 c 。这时,i 才可以正常地递增,最后终止循环。

4. 检查外部类型

假定我们有一个 C 程序,它由两个源文件组成。一个文件包含外部变量 n 的声明:

extern int n;

另一个文件中包含外部变量 n 的定义:

long n;

这是一个无效的 C 程序,因为同一个外部变量在两个文件中不能被声明为不同类型。然而编译器和连接器可能检查不出这种错误。

当这个程序运行时,究竟会发生什么情况呢?存在很多的可能情况:

  1. C 语言编译器足够“聪明”,能够检测到这类型冲突。编程者将会得到一条诊断消息,报告变量 n 在两个不同的文件中被给定了不同的类型。
  2. 读者使用的C语言实现对 int 类型的数值与 long 类型的数值在内部表示上是样的。尤其是在32位计算机上,一般都是如此处理。在这种情况下,程序很可能正常工作,就好像 n 在两个文件中都被声明为long (或int)类型一样。 本来错误的程序因为某种巧合却能够工作,这是一个很好的例子。
  3. 变量 n 的两个实例虽然要求的存储空间的大小不同,但是它们共享存储空间的方式却恰好能够满足这样的条件:赋给其中一个的值,对另一个也是有效的。这是有可能发生的。举例来说,如果连接器安排 int 类型的 n 与 long 类型的 n 的低端部分共享存储空间,这样给每个long类型的 n 赋值,恰好相当于把其低端部分赋给了 int 类型的 n。本来错误的程序因为某种巧合却能够作,这是一个比第 2 种情况更能说明问题的例子。
  4. 变量 n 的两个实例共享存储空间的方式,使得对其中一个赋值时,其效果相当于同时给另一个赋了 完全不同的值。在这种情况下,程序将不能正常工作。

因此,保证一个特定的名称的所有外部定义在每个目标模块中都有相同的类型,一般来说是程序员的责任。

考虑下面的例子,在一个文件中包含定义:

char filename[] = "/etc/passwd";

而在另一个文件中包含声明:

extern char* filename;

第一个例子中字符数组 filename 的内存布局大致如图:

第二个例子中字符指针 filename 的内存布局大致如图:

要更正本例,改法如下:

char filename[] = "/etc/passwd";// 文件 1

extern char filename[]; // 文件 2

或:

char* filename = "/etc/passwd";// 文件 1

extern char* filename; // 文件 2

现在我们回顾前面的程序:

main(){
    double s;
    s = sqrt(2);
    printf("%g\n", s);
}

这个程序在调用函数 sqrt 前没有对函数 sqrt 进行声明或定义。因此,这个程序完全等同于下面的程序:

extern int sqrt();

main(){
    double s;
    s = sqrt(2);
    printf("%g\n", s);
}

这样的写法当然是错误的。

5. 头文件

有一个好方法可以避免大部分此类问题,这个方法只需要我们接受一个简单的规则:每个外部对象只在一个地方声明。这个声明的地方一般就在一个头文件中,需要用到该外部对象的所有模块都应该包括这个头文件。特别需要指出的是,定义该外部对象的模块也应该包括这个头文件。

例如,创建一个文件叫 file.h,它包含声明:

extern char filename[];

需要用到外部对象 filename 的每个 C 文件都应该加上这样的一个语句:

#include "file.h"

最后我们选择一个 C 源文件,在其中给出 filename 的初始值。

file.c

#include "file.h"
char filename[] = "/etc/passwd";

注意,源文件 file.c 中实际上包含了 filename 的两个声明,这一点只要把 include 语句展开就可以看出:

extern char filename[];
char filename[] = "/etc/passwd";

只要源文件 file.c 中 filename 的各个声明是一致的,而且这些声明中最多只有 1 个是 filename 的定义,这样写就是合法的。

二 练习

练习4-1.

假定一个程序在一个源文件中包含了声明:

long foo;

而在另一个源文件中包含了:

extern short foo;

又进一步假定,如果给long类型的 foo 赋一个较小的值,例如37,那么short类型的foo就同时获得了一个值37。我们能够对运行该程序的硬件作出什么样的推断?如果short类型的foo得到的值不是37而是0,我们又能够作出什么样的推断?

如果把值 37 赋给 long 型的 foo,相当于同时把值 37 也赋给了short型的foo,那么这意昧着 short 型的 foo,与 long 型的foo中包含了值37的有效位的部分,两者在内存中占用的是同一区域。long 型的 foo 的低位部分与 short 型的 foo 共享了相同的内存空间,因此我们的一个可能推论就是,运行该程序的硬件是一个低位优先(little-endian:小端) 的机器。

同样道理,如果在 long 型的 foo 中存储,了值 37,而 short 型的 foo 的值却是 0,我们所用的硬件可能是一个高位优先(big-endian:大端)的机器。

注:小端就是将数字的低位放在低地址;大端则相反。

练习4-2

.本章第 4节中讨论的错误程序,经过适当简化后如下所示:

#include <stdio.h>

main()
{
	printf("qg\n"sqrt(2) ) ;
}

在某些系统中,打印出的结果是 %g 请问这是为什么?

在某些 C 语言实现中,存在着两种不同版本的 printf 函数:其中一-种实现了用于表示浮点格式的项,如 %e、%f、%g 等;而另一种却没有实现这些浮点格式。库文件中同时提供了printf 函数的两种版本,这样的话,那些没有用到浮点运算的程序,就可以使用不提供浮点格式支持的版本,从而节省程序空间、减少程序大小。

在某些系统上,编程者必须显式地通知连接器是否用到了浮点运算。而另一些系统,则是通过编译器来告知连接器在程序中是否出现了浮点运算,以自动地作出决定。

上面的程序没有进行任何浮点运算!它既没有包含 math.h 头文件,也没有声明 sqrt 函数,因此编译器无从得知 sqrt 是一个浮点函数。这个程序甚至都没有传送一个浮点参数给sqrt 函数。所以,编译器“自认合理”地通知连接器,该程序没有进行浮点运算。

那 sqrt 函数又怎么解释呢?难道 sqrt 函数是从库文件中取出的这个事实,还不足以证明该程序用到了浮点运算? 当然,sqrt 函数是从库文件中取出的这一点没错;但是,连接器可能在从库文件中取出 sqrt 函数之前,就已经作出了使用何种版本的printf 函数的决定。

注:其实 %g 被 printf 函数当作了字符串输出,后面的参数被舍弃掉了,你可以用下面这个例子来理解:

#include<stdio.h>

int main(void) {

	printf("Hello World\n", 123);

	return 0;
}

参考资料《C 缺陷与陷阱》


以上就是本次的内容,感谢观看。

如果文章有错误欢迎指正和补充,感谢!

最后,如果你还有什么问题或者想知道到的,可以在评论区告诉我呦,我在后面的文章可以加上。

最后,关注我,看更多干货!

我是程序圆,我们下次再见。

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

【C 陷阱与缺陷】(四)连接 的相关文章

  • slam原理介绍和经典算法

    1 传统slam局限性 slam算法假设的环境中的物体都是处于静态或者低运动状态的 xff0c 然而 xff0c 现实世界是复杂多变的 xff0c 因此这种假设对于应用 环境有着严格的限制 xff0c 同时影响视觉slam系统在实际场景中的
  • Git教程(李立超git和GitHub使用)

    Git教程 配置 配置name和email git config global user name 34 xxxx 34 git config global user email 34 xxx 64 xxx xxx 34 git statu
  • 需求:节目上传至MINIO后,使用mqtt进行上报

    需求 xff1a 节目上传至MINIO后 xff0c 使用mqtt进行上报 环境准备 文件管理平台 xff1a 首先需要使用minio搭建属于自己的对象存储 xff08 此步骤跳过 xff09 通信方式 xff1a MQTT方式 xff0c
  • Vue.js自定义事件的使用(实现父子之间的通信)

    vue v model修饰符 xff1a lazy number trim attrs数据的透传 xff0c 在组件 xff08 这个是写在App vue中 xff09 数据就透传到student组件中 xff0c 在template中可以
  • 简单算法——二分搜索的递归版本和非递归版本

    二分搜索 这是大家比较熟悉的算法了 xff0c 我们今天来复习一下 xff1a 前提 xff1a 二分查找要求所查找的顺序表必须是有序的 算法思路 定义left为顺序表最左端元素位置 xff0c right为顺序表右端元素位置 定义mid
  • Mysql(14)——事务

    概念 一个事务是由一条或者多条对数据库操作的SQL语句所组成的一个不可分割的单元 只有当事务中的所有操作都正常执行完了 xff0c 整个事务才会被提交给数据库 xff1b 如果有部分事务处理失败 xff0c 那么事务就要回退到最初的状态 x
  • Mysql(15)——锁机制 + MVCC(全)

    前言 事务的隔离级别在之前我们已经学习过 xff0c 那么事务隔离级别的实现原理是什么呢 xff1f 锁 43 MVCC 下面我们就来分开讲解 xff1a 表级锁 amp 行级锁 注意 xff1a 表锁和行锁说的是锁的粒度 xff0c 不要
  • DIY无人机组装与飞控参数调试记录(DJI NAZA-LITE)

    早就想玩一玩无人机 xff0c 奈何各种原因一直没有机会 xff0c 工作之后资金富足 xff0c 加上本身工作和这个相关性比较大 xff0c 于是就自己DIY了一台无人机 一 材料准备 xff1a F450机架 GPS支架 好盈乐天 20
  • Mysql(16)——日志

    前言 我们之前了解过redo log和undo log xff0c 他们是作用在InnoDb存储引擎层的 xff0c 今天我们来讲讲服务层的其他日志类型 一 错误日志 错误日志是 MySQL 中最重要的日志之一 xff0c 它记录了当 my
  • Mysql(17)——优化

    前言 一 SQL和索引优化 二 应用优化 除了优化SQL和索引 xff0c 很多时候 xff0c 在实际生产环境中 xff0c 由于数据库服务器本身的性能局限 xff0c 就必须要对上层的应用来进行一些优化 xff0c 使得上层应用访问数据
  • 项目——C++实现数据库连接池

    前言 在学习Mysql的时候 xff0c 我们都有这个常识 xff1a 对于DB的操作 xff0c 其实本质上是对于磁盘的操作 xff0c 如果对于DB的访问次数过多 xff0c 其实就是涉及了大量的磁盘IO xff0c 这就会导致MYsq
  • Redis入门——发展历程及NoSQL

    前言 随着社会的发展 xff0c 数据存储经历了诸多的过程 xff0c 这篇文章就是介绍Redis的发展由来 xff1a 1 单机Mysql时代 这种模式存在以下的瓶颈 xff1a 数据量太大 xff0c 一个机器存放不下数据的索引太大 x
  • Redis(1)——基本命令及数据类型(5+3)

    Redis的基本概念 Remote Dictionary Server xff1a 远程字典服务Redis 是一个开源 xff08 BSD许可 xff09 的 xff0c 内存中的数据结构存储系统 xff0c 它可以用作数据库 缓存和消息中
  • Redis(2)——事务机制

    Redis的事务机制 Redis的事务本质 xff1a 一组命令的集合一个事务中的所有命令都会都被序列化 xff0c 在事务执行的过程中 xff0c 会按照顺序执行 xff01 一次性 顺序性 排他性 执行一系列的命令Redis没有事务隔离
  • Redis(3)—— 持久化、发布订阅

    持久化 Redis是内存数据库 xff0c 如果不将内存中的数据库状态保存到磁盘中 xff0c 那么一旦服务器进程退出 xff0c 服务器中的数据库状态也会消失 所以Redis提供了持久化的功能 1 RDB xff08 Redis Data
  • Redis(4)——主从复制

    Redis主从复制 主从复制 xff1a 指的是将一个Redis服务器的数据 xff0c 复制到其他的Redis服务器 前者称为主节点 xff08 master leader xff0c 后者称为从节点 xff08 slave follow
  • Redis(5)——缓存穿透和雪崩

    概要 Redis缓存的使用 xff0c 极大的提高了应用程序的性能和效率 xff0c 特别是数据查询等 但同时 xff0c 它也带来了一些问题 其中 xff0c 最主要的问题就是数据一致性 xff0c 从严格意义上来讲 xff0c 这个问题
  • 复习:结构体大小的内存对齐问题

    内存对齐 内存对齐是指 xff1a 任意单个类型的数据都需要存放在能被它本身大小所能整除的地址上 基本类型的大小 char 1 short 2 int 4 long 4 long long 8 float 4 double 8 指针 4 8
  • 0.一些自己初学Solidworks的疑惑

    1 为什么要选择学习SolidWorks 首先 作为初学者 我们对一个东西并不是很了解 那么就需要别人来教我们 对吧 这些人可以是老师 可以是同学 可以是师傅 可以是网络上热心肠的大神 可以是一些培训机构 等等 首先呢 学习三维设计软件 看

随机推荐

  • LInux——五种IO模型

    Linux中的IO简述 IO主要分为以下的三种 xff1a 内存IO网络IO磁盘IO 通常我们所说的IO是后两者 xff0c Linux中无法直接操作IO设备 xff0c 必须通过系统调用请求kernal来协助完成IO的动作 xff0c 内
  • 复习:Linux中的软连接和硬连接

    前言 首先我们先来复习以下Linux的文件系统 Linux的文件系统是EXT4 以EXT4文件系统格式化磁盘时 xff0c 将磁盘分成了三个区 xff0c 分别是 xff1a 1 superblock xff1a 记录文件系统的整体信息 x
  • 复习:字节对齐的原则

    为什么需要字节对齐 xff1f 现代计算机中内存空间都是按照byte划分的 xff0c 从理论上讲似乎对任何类型的变量的访问可以从任何地址开始 xff0c 但实际情况是在访问特定类型变量的时候经常在特定的内存地址访问 xff0c 这就需要各
  • Reactor模型

    前言 首先让我们来回顾一下select poll和epoll是如何获取网络事件的 xff1a 在获取事件时 xff0c 先把我们要关心的连接传给内核 xff0c 再由内核检测 xff1a 若没有事件发生 xff0c 线程只需阻塞在这个系统调
  • Proactor模型

    前言 上一篇讲解的Reaactor是非阻塞的同步网络模式 xff0c 而Proactor是异步网络模式 至于异步IO怎么理解 xff1a 可以参考我的这一篇博客 xff1a Linux的五种IO模型 理解之后 xff1a 你就会感受到 xf
  • STL空间配置器(一级配置器及二级配置器)

    前言 在我们日常使用STL中的容器时 xff0c 我们是几乎感受不到空间配置器的存在 xff0c 因为他一直在默默工作 xff0c 我们在之前的这一篇博客中也大概介绍过 xff1a C 43 43 xff08 21 xff09 vector
  • HTTP各个版本的区别

    HTTP 1 0 短连接版本 HTTP 1 0规定浏览器与服务器只保持短暂的连接 xff0c 即每一次请求都需要与服务器建立一次TCP连接 xff0c 服务器完成请求处理后立即断开TCP连接 服务器不会跟踪每个客户也不记录过去的请求 xff
  • 实时时钟芯片DS1307的使用及驱动代码

    DS1307实时时钟芯片的介绍及驱动代码 目录 一 DS1307是什么 xff1f 二 DS1307的功能 三 DS1307的寄存器 四 代码 1 读出数据 2 写入数据 3 时间初始化设置 4 获取当前时间 五 注意事项 总结 一 DS1
  • 单片机测量NTC热敏电阻温度的方法(含程序代码)

    1 NTC介绍 NTC是负温度系数热敏电阻 xff0c 随着温度的升高 xff0c NTC的阻值会呈非线性的下降 2 硬件连接 这里采用100k 3950的热敏电阻 xff0c 100k代表的是在25 下的标准阻值 xff0c 3950是热
  • 代码编写规范

    目录 1 头文件 2 函数 3 标识符命名与定义 3 1 通用命名规则 3 2 文件命名规则 3 3 变量命名规则 3 4 函数命名规则 3 5 宏的命名规则 4 变量 5 宏 常量 6 质量保证 7 程序效率 8 注释 9 排版与格式 1
  • 1.SolidWorks各模块的学习顺序

    1 草图模块 nbsp nbsp nbsp nbsp nbsp nbsp nbsp nbsp 草图就是用线段画出零件的某一个视角的轮廓 草图是下面功能的基础 因为零件的三维建模 其实就是先画出草图 然后再通过拉伸 旋转 扫描 切除等命令生成
  • parser用法

    parser用法 导入库示例化添加参数解析参数设置属性 导入库 span class token keyword import span argparse 示例化 parser span class token operator 61 sp
  • roslaunch realsense2_camera rs_camera.launch和sudo apt-get install ros-melodic-rgbd-launch报错

    roslaunch realsense2 camera rs camera launch和roslaunch realsense2 camera rs rgbd launch报错 具体报错信息 roslaunch realsense2 ca
  • 如何设置cmake将外部文件作为资源添加到工作目录

    https stackoverflow com questions 46995733 how to set cmake in order to add txt files into working directory as resource
  • string、char*和char[]的转换

    char 和const char 的转换 const char 转 char xff08 1 xff09 为什么不能直接赋值 xff1f 这里你可以这么想 xff0c 假如const char类型字符串可以赋值给char类型 xff0c 那
  • 11-串口通信

    微控制器与外部设备的数据通信 xff0c 分为并行通信和串行通信 并行 xff1a 数据的各位同时发送或接受 xff0c 每个数据位使用一条导线 串行 xff1a 数据一位接一位地顺序发送或接收 串行通信有SPI IIC UART多种 xf
  • C语言编程规范设置 (vscode设置)

    1 打开vscode设置后 2 搜索format 3 把以下选项打上对勾 Editor Format On Paste Editor Format On Save Editor Format On Type 4 C Cpp 这一选项选择以下
  • c++ vscode 环境一键配置

    致谢 首先感谢原作者为我等初学者所做的软件 xff0c 其他文章讲了一堆的东西都没解决 xff0c 作者一个软件一步到位 xff0c 如果觉得不错的话可以star一下 xff0c 原作者视频地址 xff1a https www bilibi
  • 使用ESP8266实现单片机与上位机之间的wifi通信。

    使用ESP8266实现单片机与上位机之间的wifi通信 首先弄清楚8266的工作模式 xff0c 分别是 模式1 xff1a station xff0c 模式2 xff1a ap xff0c 模式3 xff1a station 43 ap
  • 【C 陷阱与缺陷】(四)连接

    码字不易 xff0c 对你有帮助 点赞 转发 关注 支持一下作者 微信搜公众号 xff1a 不会编程的程序圆 看更多干货 xff0c 获取第一时间更新 代码 xff0c 练习上传至 xff1a https github com hairrr