GCC 同名符号冲突解决办法

news2024/9/23 7:29:07

一、绪论

作为 C/C++ 的开发者,大多数都会清楚课本上动态库以及静态库的优缺点,在教科书上谈及到动态库的一个优点是可以节约磁盘和内存的空间,多个可执行程序通过动态库加载的方式共用一段代码段 ;而时至今日,再看看上面这一句,更多则是调侃,说的都对,就是很难实施;可能连 GNU 当初都没有意识到,后来动态库一个的用途竟然是处理同名符号冲突的问题(只有动态库才能处理同名符号问题,静态库是不行的)。

本文着重讲解的内容可以分为:

  • 同名符号冲突的两种形式
  • 同名符号的解决策略

在这个过程中,也会讲述到排查同名符号冲突的有效手段,以及与解决与同名符号相关 GNU 链接选项的作用

为了更为直观而且具有可复现操作性,本文还专门构建了一个用于演示的 Demo,本文的所有结论均以理论和实践的形式给出,为此也会花一点时间去讲解整一个 Demo 示例的组成(Demo项目为一个未提交的 git 仓库,每一条 commit 均有其对应的价值,项目可直接编译)。

二、同名符号的两种形式

在讲解同名符号之前,我觉得首先得对符号有一个清楚的认知,即符号是什么,谁会使用到符号?

符号(symbol)一词来自于GNU的ELF(Executable and Linking Format),而 ELF 文件大致上可以分为动静态库可执行程序两类,通过链接动静态库完成可执行程序的构建,所以符号是针对于链接而言的,符号冲突跟编译一毛钱关系都没有,符号冲突只跟链接有关系

1. 示例项目1

在这里插入图片描述
下面这个部分如果不敢兴趣可以跳过,这里讲解的是如果在一个 cmake 工程中实现这种比较 “鬼畜” 的操作:
整一个的项目目录如下图所示:

├── cmake
│   ├── strong_symbol.cmake
│   └── weak_symbol.cmake
├── CMakeLists.txt
├── lib
│   ├── lib_strong_shared_symbol.so
│   ├── lib_strong_static_symbol.a
│   ├── libstrong_symbol.a
│   ├── libstrong_symbol.so
│   ├── lib_weak_shared_symbol.so
│   ├── lib_weak_static_symbol.a
│   ├── libweak_symbol.a
│   └── libweak_symbol.so
├── main.cpp
└── src
    ├── strong_symbol.cpp
    ├── symbol.h
    └── weak_symbol.cpp

其中 strong_symbol.cmake 生成了 libstrong_symbol.aweak_symbol.cmake 则生成了 libweak_symbol,a, 而 CmakeLists.txt 则在生成 symbol 可执行程序时同时链接到了 libstrong_symbol.alibweak_symbol.a

要想在一个 cmake 工程时实现这种 “鬼畜” 的做法,需要注意的就是控制每一个环节的 *.o 文件,不妨通过下面三个文件的实现自己感受一下:

(1)strong_symbol.cmake

set(SRCS ${CMAKE_SOURCE_DIR}/src/symbol.h
         ${CMAKE_SOURCE_DIR}/src/strong_symbol.cpp)

SET(CMAKE_C_FLAGS "-fPIC")
SET(CMAKE_CXX_FLAGS "-fPIC")

ADD_LIBRARY(${STRONG_SYMBOL_LIB} SHARED ${SRCS})

(2)weak_symbol.cmake

set(SRCS ${CMAKE_SOURCE_DIR}/src/symbol.h
         ${CMAKE_SOURCE_DIR}/src/weak_symbol.cpp)

SET(CMAKE_C_FLAGS "-fPIC ")
SET(CMAKE_CXX_FLAGS "-fPIC ")

ADD_LIBRARY(${WEAK_SYMBOL_LIB} SHARED ${SRCS})

(3)CMakeLists.txt

cmake_minimum_required (VERSION 3.5)

project(symbol)

set(CMAKE_C_STANDARD 11)
set(CMAKE_CXX_STANDARD 14)

set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}/lib)
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}/lib)

# 动态库名称
set(WEAK_SYMBOL_LIB "weak_symbol")
set(STRONG_SYMBOL_LIB "strong_symbol")

include(${CMAKE_SOURCE_DIR}/cmake/weak_symbol.cmake)
include(${CMAKE_SOURCE_DIR}/cmake/strong_symbol.cmake)

link_directories(${CMAKE_SOURCE_DIR}/lib)

set(SRCS ${CMAKE_SOURCE_DIR}/src/symbol.h
         ${CMAKE_SOURCE_DIR}/main.cpp)
add_executable(${PROJECT_NAME} ${SRCS})
target_link_libraries(${PROJECT_NAME} ${WEAK_SYMBOL_LIB} ${STRONG_SYMBOL_LIB})

(4)symbol符号(函数)的两种实现

// strong_symbol.cpp
#include "symbol.h"

#include <iostream>

void symbol()
{
    std::cout << "strong symbol" << std::endl;
}

// weak_symbol.cpp
#include "symbol.h"

#include <iostream>

void symbol()
{
    std::cout << "weak symbol" << std::endl;
}

还是挺有意思的一种实现,可以了解一下,这种技术在完成C++单元测试Mock测试时也会被广泛使用到。

2. 符号冲突类型

GNU 的符号冲突可以分类两类,一类是显式符号冲突,另一类则是隐式符号冲突。

(1)显式符号冲突

显式符号冲突会在编译阶段直接报错,而隐式符号冲突则在编译阶段不会有任何问题;隐式符号冲突会导致程序运行时跳转至错误的符号,进而执行错误的代码段,造成不可预知的后果。

以上文演示的 示例项目1 为例,在编译时通过 gcc 传递 -Wl,-–verbose 给 ld,可以看到链接的详细过程,可以看到以下内容:

试图打开 CMakeFiles/symbol.dir/main.cpp.o 成功
CMakeFiles/symbol.dir/main.cpp.o
试图打开 ../lib/libweak_symbol.a 成功
(../lib/libweak_symbol.a)weak_symbol.cpp.o
试图打开 ../lib/libstrong_symbol.a 成功
// 这里是空着的,请注意
试图打开 /home/haorui/haorui/symbol/lib/libstdc++.so 失败
试图打开 /home/haorui/haorui/symbol/lib/libstdc++.a 失败

仔细观察可以看到链接器在打开 libweak_symbol.a 从其中读取了 weak_symbol.cpp.o,而 libstrong_symbol.a 仅仅只是被打开了,并没有读取任何 .o 文件;这是因为在链接过程中,存在符号抢占的问题,即链接器对于默认的符号加载会优先保留第一个加载的符号,忽略后续的同名符号并且持续服用第一个加载的同名符号,所以最后可执行程序的输出结果为 weak symbol .

GNU 的 ld 还提供了一个指令能够让我们实现强制归档,该指令能够强制导入静态库的符号,无论需要与否,可以简单的将 CMakeLists.txt 进行下面的修改:

// from
target_link_libraries(${PROJECT_NAME}  ${WEAK_SYMBOL_LIB} ${STRONG_SYMBOL_LIB} )
// to
target_link_libraries(${PROJECT_NAME} -Wl,--whole-archive ${WEAK_SYMBOL_LIB} ${STRONG_SYMBOL_LIB} -Wl,--no-whole-archive)

再一次观看 ld 的链接过程,可以发现链接报错,出现显式符号冲突的现象,两个静态库所包含的 .o 文件都被包含进来了:

试图打开 CMakeFiles/symbol.dir/main.cpp.o 成功
CMakeFiles/symbol.dir/main.cpp.o
试图打开 ../lib/libweak_symbol.a 成功
(../lib/libweak_symbol.a)weak_symbol.cpp.o
试图打开 ../lib/libstrong_symbol.a 成功
// 注意这个
(../lib/libstrong_symbol.a)strong_symbol.cpp.o
试图打开 /home/haorui/haorui/symbol/lib/libstdc++.so 失败
// ...
// 显式符号冲突
/usr/bin/ld: ../lib/libstrong_symbol.a(strong_symbol.cpp.o): in function `symbol()':
strong_symbol.cpp:(.text+0x0): multiple definition of `symbol()'; ../lib/libweak_symbol.a(weak_symbol.cpp.o):weak_symbol.cpp:(.text+0x0): first defined here

三、同名符号的解决策略

同名符号的解决思路有且只有两种方式,它们的原理是不相同的:

  • 利用 ELF 中的符号的强弱特性
  • 利用动态库运行时加载的特性,提供重复加载的机制

值得一提的是,无论是何种解决方式,最终的解决方式一定都是以动态库的形式给出(静态库不能解决符号冲突的原因稍后也会揭示;

1. 示例项目 2

为了能够演示这种复杂的案例,需要将 示例项目1 进行一些改动,演化为 示例项目2,它的项目视图如下:
在这里插入图片描述
项目的解释以及构建可以参考示例项目1,这里就不在赘述。

2. 利用 ELF 中的符号的强弱特性

在解释如何利用 ELF 符号的强弱特性来解决同名符号冲突之前,首先还是得回到强弱符号是针对于谁而言的,以及强弱的定义。

ELF 的强弱符号与GCC 动态库符号导出技术息息相关,在本文最前面曾经提到过符号是针对于ld 链接而言的,故强弱符号肯定也是在这上面做文章;我们知道动态库有一个特性就是运行时加载,换句话说就是可执行程序中并不包含动态库内的代码段,链接器要的只是动态库的入口符号,即直接可执行程序直接调用的接口;我们知道函数的一个特性就是封装、嵌套调用,动态库对可执行程序直接暴露的接口即为入口符号,而这些实现这些入口符号所调用符号则对链接器而言是无用,因为可执行程序中既不需要调用这些符号,也不需要这些符号的实现,这些符号是可以设置为弱符号的。

故我们可以简单的将强符号理解为对链接器可见的符号,弱符号则理解为对链接器不可见的符号;另一个方式理解则是动态库符号导出的符号即为强符号,反之则为弱符号。

如何查看动/静态库的某一符号的强弱

在 GNU 平台上动态库和静态库都是 ELF 格式的,符号的相关描述查看 ELF 即可知道,linux 下面查看的工具大致上有两个 readelfobjdump,下文以 objdump

作为演示,演示的案例使用 示例项目 2 :

_symbol 符号为 global

# objdump -x libstrong_symbol1.so
# 段内偏移 | 符号作用域 | 符号类型 | 符号所在段 | 符号对应的对象占据的内存空间大小 | 符号名
# 符号作用域 : g -> global , l -> local
# Hint : _Z13strong_symbolv 、_Z7_symbolv
0000000000001145 g     F .text  000000000000000c              _Z13strong_symbolv
0000000000000000       F *UND*  0000000000000000              __cxa_atexit@@GLIBC_2.2.5
00000000000011af g     F .text  0000000000000032              _Z7_symbolv
0000000000000000       F *UND*  0000000000000000              _ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc@@GLIBCXX_3.4
0000000000000000       F *UND*  0000000000000000              _ZNSolsEPFRSoS_E@@GLIBCXX_3.4
0000000000000000       O *UND*  0000000000000000              _ZSt4cout@@GLIBCXX_3.4
0000000000000000       F *UND*  0000000000000000              _ZNSt8ios_base4InitC1Ev@@GLIBCXX_3.4
0000000000000000  w      *UND*  0000000000000000              _ITM_deregisterTMCloneTable
0000000000000000  w      *UND*  0000000000000000              __gmon_start__
0000000000000000  w      *UND*  0000000000000000              _ITM_registerTMCloneTable
0000000000000000       F *UND*  0000000000000000              _ZNSt8ios_base4InitD1Ev@@GLIBCXX_3.4

_symbol 符号为 local

# objdump -x libstrong_symbol2.so
# 段内偏移 | 符号作用域 | 符号类型 | 符号所在段 | 符号对应的对象占据的内存空间大小 | 符号名
# 符号作用域 : g -> global , l -> local
# Hint : _Z13strong_symbolv 、_Z7_symbolv
00000000000011af l     F .text  000000000000004d              _Z7_symbolv
0000000000001000 l     F .init  0000000000000000              _init
0000000000003da8 l     O .dynamic       0000000000000000              _DYNAMIC
0000000000004048 l     O .data  0000000000000000              __TMC_END__
0000000000004000 l     O .got.plt       0000000000000000              _GLOBAL_OFFSET_TABLE_
0000000000000000       F *UND*  0000000000000000              printf@@GLIBC_2.2.5
0000000000000000  w    F *UND*  0000000000000000              __cxa_finalize@@GLIBC_2.2.5
0000000000000000       F *UND*  0000000000000000              _ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_@@GLIBCXX_3.4
0000000000001145 g     F .text  000000000000000c              _Z13strong_symbolv
0000000000000000       F *UND*  0000000000000000              __cxa_atexit@@GLIBC_2.2.5
0000000000000000       F *UND*  0000000000000000              _ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc@@GLIBCXX_3.4
0000000000000000       F *UND*  0000000000000000              _ZNSolsEPFRSoS_E@@GLIBCXX_3.4
0000000000000000       O *UND*  0000000000000000              _ZSt4cout@@GLIBCXX_3.4
0000000000000000       F *UND*  0000000000000000              _ZNSt8ios_base4InitC1Ev@@GLIBCXX_3.4
0000000000000000  w      *UND*  0000000000000000              _ITM_deregisterTMCloneTable
0000000000000000  w      *UND*  0000000000000000              __gmon_start__
0000000000000000  w      *UND*  0000000000000000              _ITM_registerTMCloneTable
0000000000000000       F *UND*  0000000000000000              _ZNSt8ios_base4InitD1Ev@@GLIBCXX_3.4

总结

先说明一下背景,在 libstrong_symbolx.so 中的 _symbol 即为同名函数 (与 libweak_symbol.so 中的 _symbol 冲突),strong_symbol 和 weak_symbol 同时都调用了 _symbol 符号,

但是它们各自 _symbol 的实现是不同的。
主程序代码如下:

int main()
{
    strong_symbol();
    weak_symbol();
    
    return 0;
}

在 libstrong_symbol1.so 的情况下输出:

strong symbol address is 0x7f5b582a01af
strong symbol
strong symbol address is 0x7f5b582a01af
strong symbol

在 libstrong_symbol2.so 的情况下输出:

strong symbol address is 0x7fc4904a01af
strong symbol
weak symbol address is 0x7fc49049b1af
weak symbol

(2)符号隐藏的两种方式

-fvisibility=hidden
这个选项是传递给编译器的,添加这个选项之后,动态库导出符号的默认行为都为本地符号,即弱符号;在程序中针对需要导出的符号需要显式添加 __attribute__((visibility("default")))关键字。

这里有一点需要注意的地方是 -fvisibility=hidden 的适用范围是 *.o 文件,即 *.cpp , 动态库链接静态库时也会将静态库中的符号导入动态库,这些符号的可见性是不受 -fvisibility=hidden 的影响的。

-Wl,–exclude-libs=ALL
这个选项是传递给链接器的,并且具有明确的使用场景,生成动态库导入静态库符号时将静态库的符号的可见性全部设置为不可见(这句话有点绕,但是需要好好理解)。
在这里插入图片描述
以上图为例,lib_weak_symbol.a 存在 symbol ,符号可见性为显性; 在生成 lib_weak_symbol.so 时,需要将 lib_weak_symbol.a 的代码段拉进 libweak_symbol.so , 在这个时候 lib_weak_symbol.a

中的 _symbol 符号也会被导入,默认是强符号;添加 -Wl,–exclude-libs=ALL 即可把 _symbol 符号的可见性设置为不可见,即弱符号;从这个案例中可以看出,-Wl,–exclude-libs=ALL 的使用场景是使用

静态库去生成动态库的过程中生效的,例如使用静态库生成静态库,那么这种情况下就不适用了。

静态库为何处理不了同名符号

其实回答这个问题很简单,对于静态库而言,没有所谓的强弱符号的说法,因为静态库的所谓代码段都会被导入到可执行程序,真要说的话那么静态库的符号只有强符号一种(也可以从另外一种角度去理解,

静态库的本质就是 *.o 文件的归档,再退一步就是 *.cpp );因为静态库的全部符号(直接使用到的或者间接使用到的)均会导入到可执行程序,同一个代码段是不允许出现同名符号的。

动态库通过一些针对性的操作之所以能够实现同名函数的兼容,实际上是通过符号隐藏,让两个同名函数放置在不同的动态库内,本质上是没有违背同一代码段不能出现同名符号的规则的。

3. 利用动态库运行时加载的特性,提供重复加载的机制

这个机制非常暴力,利用的是链接器的一个可选配置 -Wl,-Bsymbolic

正常情况下,在linux平台上(不使用-Bsymbolic),加载的目标文件中第一次出现的符号将在程序中一直被使用,不论是定义在静态可执行部分,还是在动态目标文件中

这是通过**符号抢占(symbol preemption)**来实现的。动态加载器构建符号表,所有的动态符号根据该符号表被决议。所以正常情况下,如果一个符号实例出现在动态库(DSO)中,但是已经在静态可执行文件或者之前加载的动态库中被定义,那么以前的定义也将被用于当前的动态库中。

-Bsymbolic 通过关闭DOS中的符号抢占来改变这种行为,也就是关闭了上述这种机制,使用 Bsymbolic 之后,无论是已经加载过还是没有加载过,目标符号都会被重复

加载,也就是 -Bsymbolic 是通过将共享行为变更为拷贝行为来实现同名符号兼容。

正如本文最初的调侃,动态库的一个优点是可以节约磁盘和内存的空间,多个可执行程序通过动态库加载的方式共用一段代码段,在当下为了同一个库的不同版本问题已经慢慢失去了其共享的价值了。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

数据库浅谈之常见树结构

数据库浅谈之常见树结构 HELLO&#xff0c;各位博友好&#xff0c;我是阿呆 &#x1f648;&#x1f648;&#x1f648; 这里是数据库浅谈系列&#xff0c;收录在专栏 DATABASE 中 &#x1f61c;&#x1f61c;&#x1f61c; 本系列阿呆将记录一些数据库领域相关的知识 &#…

metasploit穷举模块

目录 工具介绍 常用模块 参数介绍 工具使用 工具介绍 Metasploit框架(Metasploit Framework, MSF)是一个开源工具&#xff0c; 旨在方便渗透测试&#xff0c;它是由Ruby程序语言编写的模板化框架&#xff0c;具有很好的扩展性&#xff0c;便于渗透测试人员开发、使用定制的…

浅析C++指针与引用,栈传递的关系

目录 前言 C 堆指针 栈指针 常量指针 指针常量 引用 常量引用 总结 前言 目前做了很多项目&#xff0c;接触到各种语言&#xff0c;基本上用什么学什么&#xff0c;语言的边际就会很模糊&#xff0c;实际上语言的设计大同小异&#xff0c;只是语言具备各自的特性区别。…

Python学习-----模块3.0(正则表达式-->re模块)

目录 前言&#xff1a; 导入模块 1.re.match() 函数 &#xff08;1&#xff09;匹配单个字符 &#xff08;2&#xff09;匹配多个字符 (3) 匹配开头和结尾 2.re.search() 函数 3.re.findall() 函数 4.re.finditer() 函数 5.re.split() 函数 6.re.sub() 函数 7.re.sub…

JAVA BIO,NIO,AIO区别(建议收藏)

Java中的IO原理 首先Java中的IO都是依赖操作系统内核进行的&#xff0c;我们程序中的IO读写其实调用的是操作系统内核中的read&write两大系统调用。 操作系统内核是如何进行IO交互的呢&#xff1f; 网卡中的收到经过网线传来的网络数据&#xff0c;并将网络数据写到内存…

Flink01: 基本介绍

一、什么是Flink 1. Flink是一个开源的分布式&#xff0c;高性能&#xff0c;高可用&#xff0c;准确的流处理框架 &#xff08;1&#xff09;分布式&#xff1a;表示flink程序可以运行在很多台机器上&#xff0c; &#xff08;2&#xff09;高性能&#xff1a;表示Flink处理性…

LabVIEW使用实时跟踪查看器调试多核应用程序

LabVIEW使用实时跟踪查看器调试多核应用程序随着多核CPU的推出&#xff0c;开发人员现在可以在LabVIEW的帮助下充分利用这项新技术的功能。并行编程在为多核CPU开发应用程序时提出了新的挑战&#xff0c;例如同步多个线程对共享内存的并发访问以及处理器关联。LabVIEW可自动处理…

基于SpringBoot+vue的无偿献血后台管理系统

基于SpringBootvue的无偿献血后台管理系统 ✌全网粉丝20W,csdn特邀作者、博客专家、CSDN新星计划导师、java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取项目下载方式&#x1f345; 一、项目背…

基于BIM技术建设的重庆来福士广场项目

1. BIM工程应用概况 1.1 工程概况 重庆来福士广场项目位于朝天门广场与解放碑之间&#xff0c;直面长江与嘉陵江交汇口。是集大型购物中心、高端住宅、办公楼、公寓式酒店和酒店为一体的综合体项目。由于本项目钢结构体量大、结构复杂&#xff0c;混凝土标号高、强度大…

树莓派centos7.9(armv7hl)安装最新版宝塔linux面板 2023-0219

内存卡或usb存储器容量建议不小于16GB 1.启用 EPEL 软件源 树莓派安装centos7.9(armv7hl)以及宝塔linux面板. 2023-2-16_mklpo147的博客-CSDN博客 2.使用gcc-6 树莓派centos7.9(armv7hl)安装并切换GCC-6版本. 2023-2-18_mklpo147的博客-CSDN博客 3.更新系统 yum install -y e…

第1讲-初步认识数据库系统(测试题总结)

一、测试题 数据库系统 包含 数据库管理系统 详细版&#xff1a; 数据库管理系统DBMS是数据管理软件&#xff0c;在用户和操作系统之间。 数据库系统DBS由数据库&#xff0c;数据库管理系统&#xff08;及其应用开发工具&#xff09;、应用程序和数据库管理员DBA组成的存储、管…

使用51单片机的GPIO输出占空比可调节的PWM波

一、前言 在一些单片机或微控制器中&#xff0c;通用GPIO可以被配置为产生PWM信号。PWM即脉冲宽度调制&#xff0c;是一种用于模拟输出的技术。它可以通过改变输出信号的脉冲宽度来控制电路中的电平&#xff0c;从而实现对电路的控制。 二、什么是PWM波&#xff1f; PWM波&a…

JavaSE-线程池(5)- 建议使用的方式

JavaSE-线程池&#xff08;5&#xff09;- 建议使用的方式 虽然JDK Executors 工具类提供了默认的创建线程池的方法&#xff0c;但一般建议自定义线程池参数&#xff0c;下面是阿里巴巴开发手册给出的理由&#xff1a; 另外Spring也提供了线程池的实现&#xff0c;比如 Thread…

Flink02:Flink快速上手(Streaming WorldCount)

一、Flink快速上手 使用 &#xff08;1&#xff09;先把Flink的开发环境配置好。 &#xff08;2&#xff09;创建maven项目&#xff1a;db_flink &#xff08;3&#xff09;首先在model中将scala依赖添加进来。 &#xff08;4&#xff09;然后创建scala目录&#xff0c;因为针…

Lesson5---NumPy科学计算库

5.1 多维数组 Python拥有出色的第三方库生态系统在机器学习中&#xff0c;需要把所有的输入数据&#xff0c;都转变为多为数组的形式。score[i, j]二维数组i,j都从0开始 score[5] [85, 72, 61, 92, 80] score[2,5] [[85, 72, 61, 92, 80],[85, 72, 61, 92, 80]] score[30,5…

Linux系统之iptables应用SNAT与DNAT

目录 SNAT 一.SNAT的原理介绍 1.应用环境 2.SNAT原理 3.SNAT转换前提条件 二.开启SNAT 1.临时打开 2.永久打开 三.SNAT的转换 1.固定的公网IP地址 2.非固定的公网IP地址(共享动态IP地址) 四.SNAT实验 1.实验环境准备 2.配置web服务器&#xff08;192.168.100.100…

测试3.测试方法的分类

3.测试分类 系统测试包括回归测试和冒烟测试 回归测试&#xff1a;修改了旧的代码后&#xff0c;重新测试功能是否正确&#xff0c;有没有引入新的错误或导致其它代码产生错误 冒烟测试&#xff1a;目的是确认软件基本功能正常&#xff0c;可以进行后续的正式测试工作 按是否…

什么是 RESTful 风格?

一、什么是 REST &#xff1f; REST即表述性状态传递&#xff08;英文&#xff1a;Representational State Transfer&#xff0c;简称REST&#xff09;是Roy Thomas Fielding博士在2000年他的博士论文中提出来的一种软件架构风格。它是一种针对网络应用的设计和开发方式&#…

游戏开发 - 开发流程 - 收集

1.应用场景 主要用于了解&#xff0c;掌握游戏开发的整个流程。 2.学习/操作 1.文档阅读 复习课 | 带你梳理客户端开发的三个重点-极客时间 2.整理输出 2.1 游戏开发流程 -- 参考 按照游戏开发中的三大模块策划、程序、美术&#xff0c;画了一个图。 开发游戏的时候&#xff…

LeetCode171-Excel表列序号(进制转换问题)

LeetCode171-Excel表列序号1、问题描述2、解题思路&#xff1a;进制转换3、代码实现1、问题描述 给你一个字符串columnTitle,表示Excel表格中得列名称。返回该列名称对应得列序号。 例如&#xff1a; A -> 1 B -> 2 C -> 3 ... Z -> 26 AA -> 27 AB -> 28 …