程序的编译、链接过程分析(简洁浓缩版)!

《嵌入式工程师自我修养/C语言》系列——程序的编译、链接过程分析(简洁浓缩版)!

  • 一、程序的编译
    • 1.1 预编译指令 pragma
    • 1.2 编译过程概述
    • 1.3 符号表和重定位表
  • 二、程序的链接
    • 2.1 分段组装
    • 2.2 符号决议
      • 2.2.1 强符号与弱符号
      • 2.2.2 GNU编译器的__attribute__((weak))属性
    • 2.3 重定位
      • 2.3.1 重定位的基本原理
      • 2.3.2 重定位操作的流程
  • 三、结语

快速学习嵌入式开发其他基础知识?>>>>>>>>> 返回专栏总目录 《嵌入式工程师自我修养/C语言》<<<<<<<<<

一、程序的编译

  众所周知,程序的编译过程包括预处理、编译、汇编、链接,预处理过程中的很多预处理指令都很简单,这里就不赘述了(诸如#if #else #endif这种预处理指令),这里仅先介绍一种大家可能不太熟悉的预处理指令:#pragma。

1.1 预编译指令 pragma

  尤其在C和C++编程中,预编译指令#pragma充当着一个强大的工具,它允许我们向编译器传达特殊的指令来控制编译过程的各个方面。我们常用的几种用法列举如下:

  1. #pragma pack([n])

  在结构体数据排布中,#pragma pack([n])是一个常用的指令,用于指定结构体或联合体成员的对齐方式。默认情况下,编译器会按照特定的规则(通常是结构体最大成员的大小)对成员进行对齐,这可能会导致内存的浪费。通过使用#pragma pack,我们可以减少这种浪费,优化内存的使用。

#pragma pack(push, 1) // 保存当前对齐,设置新的对齐为1字节
struct MyStruct {
    char a;        // 1 byte
    int b;         // 4 bytes
    char c;        // 1 byte
};
#pragma pack(pop) // 恢复之前的对齐设置

上面的代码段将结构体MyStruct的对齐设置为1字节,这意味着不会有填充字节用于对齐,从而减小结构体的总大小。

  1. #pragma message(“string”)

  在复杂的项目开发过程中,#pragma message用于在编译时输出自定义的信息或提醒,它可以作为一种在代码中留下注释或提醒的手段。

#pragma message("Compiling the module...")
void myFunction() {
    // ...
}

当编译包含这段代码的文件时,编译器会显示消息"Compiling the module…",这能够帮助开发者注意到特定的代码块或编译阶段。

  1. #pragma warning

  对于想要控制特定警告的开发者来说,#pragma warning是一个非常有用的指令。它可以允许你禁用或启用特定的警告,有助于保持代码的清洁和警告的相关性。

#pragma warning(disable : 4996) // 禁用4996警告
strcpy(dest, src);
#pragma warning(default : 4996) // 恢复4996警告的默认行为

这里,我们禁用了与编号为4996的警告相关的编译器警告,然后在需要的地方恢复了它。

  1. #pragma once

  在大型项目中,防止头文件被多次包含是至关重要的,#pragma once是一个非常高效的预编译指令来实现这一点。它告诉编译器只包含一次头文件,避免了传统宏定义的冗余和潜在的命名冲突。

#pragma once
// 声明和定义...

在这个例子中,只要包含这个头文件,编译器就会确保它在编译单元中只被包含一次。

  #pragma是开发者手中的一把瑞士军刀,无论是进行内存管理优化、代码诊断、抑制编译器警告还是头文件的管理,它都能发挥巨大的作用。尽管使用#pragma需要对它们的影响有深入的了解,但合理利用它们可以在许多方面提高代码的质量和编译的效率。

Tip📌:每个编译器对#pragma的支持可能略有不同,因此在跨编译器项目中使用时请务必检查兼容性。

1.2 编译过程概述

  从C程序到可执行文件,整个编译过程如下所示,编译程序(如gcc、arm-linux-gcc)会调用不同的工具来完成不同阶段的任务。

  • 预处理器:将源文件main.c经过预处理变为main.i;
  • 编译器:将预处理后的main.i编译为汇编文件main.s;
  • 汇编器:将汇编文件main.s编译为目标文件main.o;
  • 链接器:将各个目标文件main.o、sub.o链接成可执行文件a.out。
    在这里插入图片描述

  最后生成的可执行文件a.out 其实也是目标文件(object file),唯一不同的是,a.out是一种可执行的目标文件。目标文件一般可以分为3种:可重定位的目标文件(relocatable files)、可执行的目标文件(executable files)和可被共享的目标文件(shared object files)

Tip📌:汇编器生成的目标文件是可重定位的目标文件,是不可执行的,需要链接器经过链接、重定位之后才能运行。可被共享的目标文件一般以共享库的形式存在,在程序运行时需要动态加载到内存,跟应用程序一起运行。

  从C源文件到汇编文件的转换,其实就是将C文件中的程序代码块、函数转换为汇编程序中的代码段,将C程序中的全局变量、静态变量、常量转换为汇编程序中的数据段、只读数据段。这已经是及其简洁的说法了,实际上编译过程可以分为以下6步,每一步都是可以深入探讨的,但我们没必要了解的那么深入,简单知道其作用即可,他们分别是:词法分析、语法分析、语义分析、中间代码生成、汇编代码生成、目标代码生成。

  汇编文件中的汇编指令就是二进制指令的助记符,唯一的差异就是汇编语言的程序结构需要使用各种伪操作来组织。汇编文件经过汇编器汇编后,处理掉各种伪操作命令,就是二进制目标文件了。每一个c源文件经过汇编都会生成对应的目标文件,这些目标文件是不可执行的,属于可重定位的目标文件,它们要经过链接器重定位、链接之后,才能组装成一个可执行的目标文件。这些目标文件都是以零地址为链接起始地址进行链接的,比如上图中main.c 和 sub.c经过汇编生成的可执行文件main.o 和 sub.o,如果通过readelf -S main.o这个-S选项分别查看他们节头表的话,将发现两个文件的起始段信息都是从0地址开始往后偏移。在每个可重定位目标文件中,函数或变量的地址其实就是它们在文件中相对于零地址的偏移

1.3 符号表和重定位表

  在编译和链接程序的过程中,编译器将源代码编译成目标文件,每个目标文件都是假设自己从零地址开始。在链接过程中,链接器需要把多个目标文件合并成一个可执行文件,这时就需要更新文件中的地址引用,这个更新地址的过程称为重定位。

  链接器如何知道哪些地址需要更新呢?答案是通过重定位表,这是一种记录了需要更新地址的符号信息的数据结构。每个目标文件都包含有自己的重定位表,在链接时,链接器会查看这些表,然后根据最终确定的内存布局来更新符号地址。(可以使用readelf -s=r main.o查看目标文件的重定位表)

  同时,每个目标文件还包含一个符号表,这个表列出了文件中所有的符号(如函数和变量名),无论这些符号是否需要重定位。链接器通过符号表来解析不同目标文件之间的符号引用关系。(可以使用readelf -s main.o查看目标文件的符号表)

  例如,如果main.o文件中的main函数调用了sub.o文件中的add和sub函数,在链接过程中,add和sub函数的地址可能会改变。链接器会在这个过程结束后,根据重定位表中的信息更新这些函数引用的地址。这就确保了在最终生成的可执行文件中,所有的函数和变量引用都指向正确的地址。

Tip📌:符号表本质上是一个结构体数组,在ARM平台下,定义在Linux内核源码的/arch/arm/include/asm/elf.h文件中。

  在编译和链接程序的时候,符号表中的每个符号都有一个符号值和类型。符号值是符号在内存中的地址;类型描述了符号的性质和用途。这些类型包括:

符号类型描述
OBJECT表示变量
FUNC表示函数或可执行代码
FILE关联当前目标文件名称
SECTION与一个section有关,用于重定位
COMMON表示公用块数据对象,是全局弱符号
TLS变量存储在线程局部存储
NOTYPE符号类型未指定或未知

Tip📌:符号值既可以是绝对地址(通常在可执行目标文件中出现),也可以是相对地址(常在可重定位目标文件中出现)。

二、程序的链接

  通过编译环节我们得到了各个文件对应的目标文件,每个目标文件都是由section构成的可重定位目标文件,其中还有两个重要的表:重定位表和符号表。本节将简述链接器具体如何应用这两个表。实际上,链接过程主要分三步:分段组装、符号决议和重定位

2.1 分段组装

  分段组装过程其实就是链接器将各个目标文件的各类型的段组装在一起,比如将各个目标文件的代码段放在一起,数据段放在一起,建立一个全局符号表收集各个目标文件中的符号信息(注意,此时全局符号表中各个符号的地址仍然是原来在各个目标文件中的地址,即都是以零地址作为起始地址的)

  在链接程序时需要指定一个链接起始地址(因为链接生成的可执行文件最终是要被加载到内存中执行的),该地址一般也是程序要加载到内存中的地址。同时,还要考虑链接过程中各个section的排布顺序等方面,这些功能都是通过连接脚本实现的。

  这个脚本文件里不仅规定了各个段的组装顺序、起始地址、位置对齐等信息,同时对输出的可执行文件格式、运行平台、入口地址等信息做了详细的描述。链接器就是根据链接脚本定义的规则来组装可执行文件的,并最终将这些信息以section的形式保存到可执行文件的ELF Header中。

  一个简单的链接脚本示例如下所示,关于链接脚本的详细内容,将在另一篇文章中阐述,届时将在这里放置文章链接。

/* Specify the output format and the target architecture */
OUTPUT_FORMAT("elf64-x86-64")
OUTPUT_ARCH(i386:x86-64)

ENTRY(_start)	/* Define the entry point of the executable */

/* Define the memory layout of the sections */
SECTIONS
{
    . = 0x10000;	/* Define the starting memory address for the code */

    .text : {
        *(.text) /* Include all .text sections from input files */
    }

    .data : {
        *(.data) /* Include all .data sections from input files */
    }

    .bss : {
        *(.bss) /* Include all .bss sections from input files */
    }
}

  在这个链接脚本中,我们定义了输出格式为elf64-x86-64,目标架构为i386:x86-64。脚本中ENTRY指令指定了程序的入口点为_start。在SECTIONS块中,我们为.text.data.bss三个部分指定了它们在内存中的起始地址和排布顺序。

Tips📌:
  1. 不同的编译器、不同的操作系统,链接脚本的文件名后缀一般也不一样。
  2. 在一个由带有MMU的CPU搭建的嵌入式系统中,程序的链接起始地址往往都是一个虚拟地址,程序运行过程中还需要地址转换,通过MMU将虚拟地址转换为物理地址,然后才能访问内存。

2.2 符号决议

  在软件开发的链接阶段,程序中的各个符号(如变量和函数名)需要被合并以形成最终的可执行文件。符号冲突通常是指不同的对象或源文件中含有相同名称的全局符号。解决这种冲突的规则可以用一句俗语概括:“一山不容二虎,强弱可以共存,体积大者胜出”。为了更好理解这些概念,我们首先需要区分“强符号”与“弱符号”。

2.2.1 强符号与弱符号

  强符号通常是指用户定义的全局变量和函数,因为它们有具体的值或实现。而弱符号则可能来自于未初始化的全局变量或者是一些特殊的编译器指令(如GNU编译器的__attribute__((weak)))声明的变量和函数。在链接时,强符号与弱符号的处理有所不同,这导致了一些链接时的规则和行为。

——链接规则解释

  1. 一山不容二虎:这条规则指出如果两个强符号冲突(即两个强符号的名称相同),则链接会失败,因为链接器不允许有两个相同名称的强符号存在。这就像一山不能容纳两只虎一样,不可能有两个相同的实体共存。

  2. 强弱可以共存:当一个强符号和一个弱符号名称相同时,强符号会胜出,链接器会选择强符号,而忽略弱符号。这是因为强符号提供了明确的定义,而弱符号则相对模糊。

  3. 体积大者胜出:如果两个符号都是弱符号,并且都有相同的名称,链接器会选择那个体积更大的符号(即占用内存空间更多的符号),并且忽略体积较小的那个。如果它们大小相同,则任选其一。

——举例如下

  假设我们有两个不同的源文件,分别定义了以下全局变量和函数:

  源文件1:

int value;        // 弱符号,因为未初始化的全局变量
void foo() {}     // 强符号,因为是一个具体定义的函数

  源文件2:

int value = 1;    // 强符号,因为是初始化的全局变量
void foo() {}     // 强符号,同样是一个具体定义的函数

  在链接这两个源文件时,会发生以下情况:

  • 对于变量value,源文件1中的是弱符号,而源文件2中的是强符号。根据“强弱可以共存”的规则,链接器会选择源文件2中的value
  • 对于函数foo,两个源文件中都是强符号。由于“一山不容二虎”的规则,这将导致链接错误,除非某种方式可以解决这个冲突,比如静态链接库或者更改其中一个符号的名称。

2.2.2 GNU编译器的__attribute__((weak))属性

  __attribute__((weak))是GNU编译器提供的一个功能强大的属性,可以用于声明弱符号。这允许开发者为全局变量和函数提供默认的定义,同时还可以被其他模块中的强符号覆盖。这在创建库和模块化编程时非常有用,因为它允许用户扩展或修改默认行为,而无需修改原始库代码。

  为了深入理解如何在实际编码中应用强弱符号规则,尤其是利用GNU编译器的__attribute__((weak))属性,我们来扩展下上面的例子,进一步示范这个特性的用法。

——举例如下

  假设我们现在有三个源文件,分别是main.clibdefault.cuserlib.c

  • libdefault.c是一个库文件,提供了一些默认的实现。
  • userlib.c是用户自定义的库,可能会提供一些默认实现的替代版本。
  • main.c是主程序,将使用这些库提供的功能。

源文件 libdefault.c:

#include <stdio.h>

void __attribute__((weak)) foo() {
    printf("Default foo implementation\n");
}

int __attribute__((weak)) value = 42;

  这里,foo函数和value变量都被声明为弱符号,意味着如果存在同名的强符号,它们可以被覆盖。

源文件 userlib.c (用户提供了自己的实现,但这不是必须的):

#include <stdio.h>

void foo() {
    printf("User-defined foo implementation\n");
}

int value = 100;

  在userlib.c中,用户提供了foo函数和value变量的自定义实现,不使用__attribute__((weak)),因此这两个符号都是强符号。

源文件 main.c:

#include <stdio.h>

extern void foo();
extern int value;

int main() {
    foo();
    printf("Value is %d\n", value);
    return 0;
}

  在main.c中,主程序声明了对foo函数和value变量的外部引用,并在主函数中调用foo和访问value

——链接和运行结果

当我们将这三个文件一起编译和链接时:

  • 如果userlib.c被包含在编译过程中,那么它里面的foovalue将作为强符号,覆盖libdefault.c中的弱符号定义。
  • 如果userlib.c没有被编译链接,那么libdefault.c中的弱符号实现将会被采用。

假设userlib.c被包含,程序输出将是:

User-defined foo implementation
Value is 100

如果userlib.c未被包含,输出将是:

Default foo implementation
Value is 42

  这个例子展示了如何通过GNU编译器的__attribute__((weak))属性来设计灵活的接口和可扩展的程序结构,允许易于维护和升级的同时,也提供了默认的行为实现。

2.3 重定位

  经过符号决议,我们解决了链接过程中多文件符号冲突的问题。可执行文件的符号表中的每个符号虽然都确定下来
了,但是符号表中的每个符号值,也就是每个函数、全局变量的地址,还是原来各个目标文件中的值,还都是基于零地址的偏移。链接器将各个目标文件重新分解组装后,各个段的起始地址已经发生了变化。当前重组后的文件中需要为这些符号指定新的地址,这就是重定位过程的工作。

  重定位是编译链接过程中的一个关键步骤,它负责调整和转换目标文件中的符号引用,确保程序中的每个符号引用都指向正确的内存地址。经过符号决议后,代码和数据已经从它们最初的目标文件中被重新组织,可能被放置在内存中的不同位置。因此,链接器必须更新这些引用,以反映它们在内存中的实际位置。

2.3.1 重定位的基本原理

  当链接器合并多个目标文件时,它会创建两个主要的表:符号表和重定位表。符号表包含了所有符号的名称和它们的属性(如是否为强符号或弱符号)。在链接过程中,符号决议会为每个全局符号赋予一个唯一的地址。重定位表则记录了所有需要更新内存地址的地方,这些位置在源代码中通常是相对引用(例如,从函数的开头到一个全局变量的偏移量)。

  重定位表包含三个关键的信息:

  1. 偏移(Offset):需要重定位的代码或数据位置相对于段开始的偏移。
  2. 符号(Symbol):此重定位项对应到符号表中的符号。
  3. 类型(Type):重定位类型,指导链接器如何进行重定位。

2.3.2 重定位操作的流程

  链接器进行重定位的基本步骤如下:

  1. 确定基地址:链接器为每个段(代码段、数据段等)确定一个基地址。
  2. 遍历重定位表:链接器遍历每个目标文件的重定位表,查找需要修正的引用。
  3. 计算新地址:对于每个表项,链接器根据符号表中的符号地址、重定位表中的偏移和确定的基地址来计算新地址。
  4. 更新引用:链接器将目标文件中的相对地址更新为实际的内存地址。

——举例如下

  假设我们有两个简单的C文件,main.clibrary.c,它们被编译成两个目标文件main.olibrary.o

  源文件 library.c:

int shared_val = 10;

void library_function() {
    // 函数实现
}

  源文件 main.c:

extern int shared_val;

int main() {
    return shared_val;
}

  编译这两个文件将产生目标文件,这时shared_val的地址还没有最终确定。链接器通过重定位表来识别main.c中对shared_val的引用,这个引用在main.o中是一个基于零地址的偏移。

  当链接器将main.olibrary.o链接成一个可执行文件时,它可能决定将shared_val放在地址0x10000处。链接器会在main函数中重定位对shared_val的引用,将它从偏移更改为实际地址0x10000

  通过符号表和重定位表的配合,链接器能够解决多个目标文件中的符号冲突,并确保所有符号引用在最终的执行文件中都正确指向它们的实际地址。重定位是确保代码和数据在内存中正确布置的关键过程,没有它,程序将无法正确运行。

三、结语

  至此,整个编译链接过程就结束了,最终生成的目标文件就是可执行的目标文件了。实际上,编译链接过程每一环节都有很多要注意和学习的地方,本文只是简单的梳理了一个框架和比较重要的知识,还需要在以后的学习中不断扩充相关知识。

快速学习嵌入式开发其他基础知识?>>>>>>>>> 返回专栏总目录 《嵌入式工程师自我修养/C语言》<<<<<<<<<

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/511934.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

了解与生成火焰图

目录 一、如何看懂火焰图 1、基本特征 2、基本分类 二、如何生成火焰图 1、捕获调用栈 2、折叠栈 3、转换为 svg 格式 4、展示 svg 一、如何看懂火焰图 1、基本特征 &#xff08;1&#xff09;纵轴&#xff1a;即每一列代表一个调用栈&#xff0c;每一个格子代表一个函…

智能仓储变革在即,从业者该何去何从?

导语 大家好&#xff0c;我是智能仓储物流技术研习社的社长&#xff0c;你的老朋友&#xff0c;老K。行业群 新书《智能物流系统构成与技术实践》 随着2024年的到来&#xff0c;物流和仓储行业正处于一个技术革命的关键时刻。人工智能&#xff08;AI&#xff09;的融入不仅预示…

【二叉树】Leetcode 437. 路径总和 III【中等】

路径总和 III 给定一个二叉树的根节点 root &#xff0c;和一个整数 targetSum &#xff0c;求该二叉树里节点值之和等于 targetSum 的 路径 的数目。 路径 不需要从根节点开始&#xff0c;也不需要在叶子节点结束&#xff0c;但是路径方向必须是向下的&#xff08;只能从父节…

Zabbix6 - Centos7部署Grafana可视化图形监控系统配置手册手册

Zabbix6 - Centos7部署Grafana可视化图形监控系统配置手册手册 概述&#xff1a; Grafana是一个开源的数据可视化和监控平台。其特点&#xff1a; 1&#xff09;丰富的可视化显示插件&#xff0c;包括热图、折线图、饼图&#xff0c;表格等&#xff1b; 2&#xff09;支持多数据…

[源码] Android 上的一些快捷方式,如通知、快捷方式等

目录 一、通知0. 配置权限1. 测试发送通知代码2. 打开通知设置界面代码3. 前台服务创建常驻通知 二、快捷方式1. 测试添加动态快捷方式代码 三、开发者图块四、桌面小部件 基于jetpack compose 框架的使用代码 一、通知 参见 官方文档 0. 配置权限 <uses-permission andr…

REST API的指纹验证机制

前端或者客户端涉及数据相关的请求都是不安全的&#xff0c;从某种意义上只能通过一些手段降低请求不被容易使用。本来来介绍一种基于 JWT 的指纹机制。 关于 JWT 令牌机制就不详细介绍了。在 JWT 令牌中包含系统 JWT 指纹可以带来安全改进&#xff0c;而不会给用户带来任何不…

RocketMQ 消费者源码解读:消费过程、负载原理、顺序消费原理

B站学习地址 上一遍学习了三种常见队列的消费原理&#xff0c;本次我们来从源码的角度来证明上篇中的理论。 1、准备 RocketMQ 版本 <!-- RocketMQ --> <dependency><groupId>org.apache.rocketmq</groupId><artifactId>rocketmq-spring-boot-s…

yolov5关键点检测-实现溺水检测与警报提示(代码+原理)

基于YOLOv5的关键点检测应用于溺水检测与警报提示是一种结合深度学习与计算机视觉技术的安全监控解决方案。该项目通常会利用YOLOv5强大的实时目标检测能力&#xff0c;并通过扩展或修改网络结构以支持人体关键点检测&#xff0c;来识别游泳池或其他水域中人们的行为姿态。 项…

常关型p-GaN栅AlGaN/GaN HEMT作为片上电容器的建模与分析

来源&#xff1a;Modeling and Analysis of Normally-OFF p-GaN Gate AlGaN/GaN HEMT as an ON-Chip Capacitor&#xff08;TED 20年&#xff09; 摘要 提出了一种精确基于物理的解析模型&#xff0c;用于描述p-GaN栅AlGaN/GaN高电子迁移率晶体管&#xff08;HEMT&#xff09…

【Linux】Vim编辑器

专栏文章索引&#xff1a;Linux 目录 在Vim编辑器中&#xff0c;一个Tab键相当于几个空格&#xff1f; 在Vim编辑器中&#xff0c;一个Tab键相当于几个空格&#xff1f; 在Vim编辑器中&#xff0c;默认情况下&#xff0c;一个Tab键相当于8个空格。 这是Vim的默认设置&#x…

【C++】哈希之位图

目录 一、位图概念二、海量数据面试题 一、位图概念 假如有40亿个无重复且没有排序的无符号整数&#xff0c;给一个无符号整数&#xff0c;如何判断这个整数是否在这40亿个数中&#xff1f; 我们用以前的思路有这些&#xff1a; 把这40亿个数遍历一遍&#xff0c;直到找到为…

鸿蒙OS元服务开发:【(Stage模型)设置悬浮窗】

一、设置悬浮窗说明 悬浮窗可以在已有的任务基础上&#xff0c;创建一个始终在前台显示的窗口。即使创建悬浮窗的任务退至后台&#xff0c;悬浮窗仍然可以在前台显示。通常悬浮窗位于所有应用窗口之上&#xff1b;开发者可以创建悬浮窗&#xff0c;并对悬浮窗进行属性设置等操…

frp内网穿透之(反向代理nginx)

通过公网 https 连接访问内网&#xff08;局域网&#xff09;本地http服务如下&#xff1a; 1.准备工作 ​ 想要实现内网穿透功能首先我们需要准备&#xff1a; 一台公网服务器&#xff08;用作frps的服务端&#xff09;一台需要做转发的内网服务器&#xff08;用作frpc的客…

D-迷恋网游(遇到过的题,做个笔记)

我的代码&#xff1a; #include <iostream> using namespace std; int main() {int a, b, c; //a表示内向&#xff0c;b表示外向&#xff0c;c表示无所谓cin >> a >> b >> c; //读入数 if (b % 3 0 || 3-b % 3 < c) //如果外向的人能够3人组成…

Golang Channel底层实现原理

1、本文讨论Channel的底层实现原理 首先&#xff0c;我们看Channel的结构体 简要介绍管道结构体中&#xff0c;几个关键字段 在Golang中&#xff0c;管道是分为有缓冲区的管道和无缓冲区的管道。 这里简单提一下&#xff0c;缓冲区大小为1的管道和无缓冲区的管道的区别&…

Android14之BpBinder构造函数Handle拆解(二百零四)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 优质专栏&#xff1a;Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 优质专栏&#xff1a;多媒…

详解人工智能(概念、发展、机遇与挑战)

前言 人工智能&#xff08;Artificial Intelligence&#xff0c;简称AI&#xff09;是一门新兴的技术科学&#xff0c;是指通过模拟、延伸和扩展人类智能的理论、方法、技术和应用系统&#xff0c;以实现对人类认知、决策、规划、学习、交流、创造等智能行为的模拟、延伸和扩展…

Linux 线程互斥、互斥量、可重入与线程安全

目录 一、线程互斥 1、回顾相关概念 2、抢票场景分析代码 多个线程同时操作全局变量 产生原因 如何解决 二、互斥量 1、概念 2、初始化互斥量&#xff1a; 方法1&#xff1a;静态分配 方法2&#xff1a;动态分配 3、销毁互斥量&#xff1a; 4、加锁和解锁 示例抢…

MySQL 8.0.13安装配置教程

写个博客记录一下&#xff0c;省得下次换设备换系统还要到处翻教程&#xff0c;直接匹配自己常用的8.0.13版本 1.MySQL包解压到某个路径 2.将bin的路径加到系统环境变量Path下 3.在安装根目录下新建my.ini配置文件&#xff0c;并用编辑器写入如下数据 [mysqld] [client] port…

基于jsp网上教师点评系统

基于jsp网上教师点评系统 关键词&#xff1a;教师点评 信息技术 JSP技术 系统实现 首页 评分规则 教室信息 后台首页 相关技术介绍 B/S架构 对于架构&#xff0c;听起来说我们可能比较陌生&#xff0c;但对于通俗的语法讲。他的访问方式是通过网址还是说通过点图标这…