编译期链接时共享库搜索路径优先级实验

news2024/9/9 0:25:01

编译期链接时共享库搜索路径优先级实验

  • 前言
  • 实验环境
  • 目录说明
  • 准备工作
  • 单独测试
    • 不配置路径
    • 默认路径
    • LIBRARY_PATH
    • -L
  • 优先级测试
    • 默认路径和LIBRARY_PATH
    • -L和默认路径
  • DEBUG模式
    • 编译器配置详细信息
    • 链接器详细信息
    • DEBUG总结
    • 验证
  • 默认路径>LIBRARY_PATH原因
  • 附录
    • 库文件源码
    • 主程序源码
    • makefile

前言

《共享库链接和加载时的路径搜索优先级》中提到,使用g++时,共享库在编译期链接时的库路径搜索优先级为:-L指定的路径>LIBRARY_PATH记录的路径>默认路径

本实验分三步验证上述结论
①单独测试每种方法指定的路径的可行性
②对比测试三种方法间的优先级
③使用DEBUG模式,查看链接器输出的详细信息,二次验证上述结论

值得注意的是,我看网上都说LIBRARY_PATH指定的路径优先级要大于默认路径的优先级,但是就我的测试结果来看,结论是相反的(可能是我使用了g++而不是直接使用底层的ld?)。

实验环境

操作系统:Ubuntu 20.04
编译器:g++-11.4.0
make:GNU Make 4.2.1

目录说明

项目的目录结构如下:

.
├── lib
├── obj
├── libhello.cpp
├── libhello_alt.cpp
├── main.cpp
└── makefile

其中:

  • lib为存放共享库的文件夹
  • obj为存放可重定位目标文件的文件夹
  • libhello.cpplibhello_alt.cpp为共享库源码(用于模拟不同版本的共享库),他们中都只有一个hello函数,两个hello函数的函数签名完全相同。其中libhello.cpp将被编译为libhello.so.1.1.0(soname为libhello.so.1),libhello_alt.cpp将被编译为libhello.so.2.1.0(soname为libhello.so.2
  • mian.cpp为主函数,其中调用了hello函数
  • makefile为自动化构建脚本

在附录中,我将提供本次实验涉及到的代码。

准备工作

在终端中进入项目路径,并输入make,会在./lib下生成libhello.so.1.1.0libhello.so.2.1.0,在./obj下生成main.o
生成后项目的目录结构如下:

.
├── lib
│   ├── libhello.so.1.1.0	
│   └── libhello.so.2.1.0
├── obj
│   └── main.o
├── libhello.cpp
├── libhello_alt.cpp
├── main.cpp
└── makefile

单独测试

不配置路径

不做任何路径的配置并且不在默认路径下放置libhello.so文件,查看是否可以将main.ohello的共享库文件链接成功。

直接使用makefile中预设好的命令即可完成上述操作:

make main_none

输出:
在这里插入图片描述
可以看到由于我们没有配置任何额外的搜索路径,并且没有在默认搜索路径下放置libhello.so文件,链接器就找不到相应的共享库文件,就会链接失败。

单次实验结束后,使用make clean命令清除本次实验生成的文件,然后再次使用make命令重新生成共享库文件和可重定位目标文件。(每次做完一个小实验,都要重复此步骤,后不赘述)

默认路径

libhello.so.1.1.0拷贝至默认搜索路径/usr/lib,并在/usr/lib下创建一个软链接(libhello.so)指向它,然后进行链接操作,查看是否可以将main.ohello的共享库文件链接成功。

直接使用makefile中预设好的命令即可完成上述操作:

make main_default

输出:
在这里插入图片描述
没有报错。

然后使用readelf -d查看可执行文件的动态段信息,可见链接成功,共享库的soname已经被写入到可执行文件的动态段信息中了。
在这里插入图片描述

LIBRARY_PATH

创建路径/opt/hellolib,将libhello.so.1.1.0拷贝至/opt/hellolib,并在/opt/hellolib下创建一个软链接(libhello.so)指向它。然后将/opt/hellolib添加至LIBRARY_PATH并进行main.ohello的共享库文件的链接操作,查看是否可以链接成功。

直接使用makefile中预设好的命令即可完成上述操作:

make main_library_path

输出:
在这里插入图片描述
没有报错。

然后使用readelf -d查看可执行文件的动态段信息,可见链接成功,共享库的soname已经被写入到可执行文件的动态段信息中了。
在这里插入图片描述

-L

创建路径/opt/hellolib,将libhello.so.1.1.0拷贝至/opt/hellolib,并在/opt/hellolib下创建一个软链接(libhello.so)指向它,然后添加链接选项-L/opt/hellolib并进行链接操作,查看是否可以将main.ohello的共享库文件链接成功。

直接使用makefile中预设好的命令即可完成上述操作:

make main_l

输出:
在这里插入图片描述
没有报错。

然后使用readelf -d查看可执行文件的动态段信息,可见链接成功,共享库的soname已经被写入到可执行文件的动态段信息中了。
在这里插入图片描述

优先级测试

默认路径和LIBRARY_PATH

  • ①将libhello.so.2.1.0拷贝至默认搜索路径/usr/lib,并在/usr/lib下创建一个软链接(libhello.so)指向它。
  • ②创建路径/opt/hellolib,将libhello.so.1.1.0拷贝至/opt/hellolib,并在/opt/hellolib下创建一个软链接(libhello.so)指向它。
  • ③将/opt/hellolib添加至LIBRARY_PATH并进行main.ohello的共享库文件的链接操作。

直接使用makefile中预设好的命令即可完成上述操作:

make cmp_default_libpath

输出:
在这里插入图片描述

然后使用readelf -d查看可执行文件的动态段信息,可见链接成功,并且链接的是默认路径下的共享库文件libhello.so.2.1.0(其soname为libhello.so.2)。因此可以得出结论:默认路径搜索优先级要高于LIBRARY_PATH指定的路径的搜索优先级。
在这里插入图片描述
对于上述结论,将会在后文的DEBUG模式中给出更详细的验证。

-L和默认路径

  • ①创建路径/opt/hellolib,将libhello.so.2.1.0拷贝至/opt/hellolib,并在/opt/hellolib下创建一个软链接(libhello.so)指向它。
  • ②将libhello.so.1.1.0拷贝至默认搜索路径/usr/lib,并在/usr/lib下创建一个软链接(libhello.so)指向它。
  • ③添加链接选项-L/opt/hellolib并进行main.ohello的共享库文件的链接操作。

直接使用makefile中预设好的命令即可完成上述操作:

make cmp_l_default

输出:
在这里插入图片描述

然后使用readelf -d查看可执行文件的动态段信息,可见链接成功,并且链接的是-L指定路径下的共享库文件libhello.so.2.1.0(其soname为libhello.so.2)。因此可以得出结论:-L指定路径搜索优先级要高于默认搜索路径的搜索优先级。

在这里插入图片描述
对于上述结论,将会在后文的DEBUG模式中给出更详细的验证。

DEBUG模式

在makefile中我添加了一个用于对比三种路径优先级的目标cmp_all,其中

  • -L指定路径为/opt/hellolib_L
  • 默认路径为/usr/lib
  • LIBRARY_PATH指定路径为/opt/hellolib.so文件(libhello.so.1.1.0的软链接)仅放置于此路径下。

此外我还预设了一个DEBUG模式,开启DEBUG模式可以查看编译过程的详细信息,开启的方法就是在命令后面添加DEBUG_MODE=1,例如:

make cmp_all DEBUG_MODE=1

下面我们就使用DEBUG模式运行cmp_all查看其输出(输出信息很多,我截取关键部分讲解):

编译器配置详细信息

我们先看一下gcc在编译过程中输出的编译器配置详细信息:

在这里插入图片描述
图片中的文字内容如下:

LIBRARY_PATH=
	/usr/lib/gcc/x86_64-linux-gnu/11/:
	/usr/lib/gcc/x86_64-linux-gnu/11/../../../x86_64-linux-gnu/:
	/usr/lib/gcc/x86_64-linux-gnu/11/../../../../lib/:
	/lib/x86_64-linux-gnu/:
	/lib/../lib/:
	/usr/lib/x86_64-linux-gnu/:
	/usr/lib/../lib/:
	/opt/hellolib/:
	/usr/lib/gcc/x86_64-linux-gnu/11/../../../:
	/lib/:
	/usr/lib/

我们可以发现编译器列出了系统环境变量LIBRARY_PATH的内容,包含:

  • ①我们向环境变量添加的/opt/hellolib/,其所处位置应该是由编译器规定的
  • ②系统默认的库路径(/usr/lib/lib),位于最后
  • ③根据编译器配置自动添加的路径,如/usr/lib/gcc/x86_64-linux-gnu/11/

然后再往下看,COLLECT_GCC_OPTIONS列出了传递给g++的一些选项:

在这里插入图片描述
图片中的文字内容如下(省略了一部分不需要关注的):

COLLECT_GCC_OPTIONS=...
	-L/opt/hellolib_L
	-L/usr/lib/gcc/x86_64-linux-gnu/11 
	-L/usr/lib/gcc/x86_64-linux-gnu/11/../../../x86_64-linux-gnu 
	-L/usr/lib/gcc/x86_64-linux-gnu/11/../../../../lib 
	-L/lib/x86_64-linux-gnu 
	-L/lib/../lib 
	-L/usr/lib/x86_64-linux-gnu 
	-L/usr/lib/../lib 
	-L/opt/hellolib 
	-L/usr/lib/gcc/x86_64-linux-gnu/11/../../.. 
	...

可以发现:

  • ①我们通过-L显式添加的路径/opt/hellolib_L被排在了最前面
  • LIBRARY_PATH中的路径(除了/usr/lib//lib/,原因暂时未知),都被加上-L并传给了COLLECT_GCC_OPTIONS,并排在/opt/hellolib_L之后。

链接器详细信息

然后我们再看链接器输出的详细信息:
在这里插入图片描述
图片中的文字内容如下:

SEARCH_DIR("=/usr/local/lib/x86_64-linux-gnu"); 
SEARCH_DIR("=/lib/x86_64-linux-gnu"); 
SEARCH_DIR("=/usr/lib/x86_64-linux-gnu"); 
SEARCH_DIR("=/usr/lib/x86_64-linux-gnu64"); 
SEARCH_DIR("=/usr/local/lib64"); 
SEARCH_DIR("=/lib64"); 
SEARCH_DIR("=/usr/lib64"); 
SEARCH_DIR("=/usr/local/lib"); 
SEARCH_DIR("=/lib"); 
SEARCH_DIR("=/usr/lib"); 
SEARCH_DIR("=/usr/x86_64-linux-gnu/lib64"); 
SEARCH_DIR("=/usr/x86_64-linux-gnu/lib");

SEARCH_DIR指令是用来指定链接器在搜索动态和静态库文件时应当考虑的目录,这些路径通常包括系统的标准库目录,如/usr/lib/lib等。但是注意,通过-L指定的路径会在运行时临时添加到SEARCH_DIR列表的前面,即-L指定的路径搜索优先级更高。

DEBUG总结

至此,我们可以简单总结一下上述信息:

  • 我们设置的LIBRARY_PATH的值会传给编译器
  • 编译器根据自己的配置以及我们手动赋予的LIBRARY_PATH变量的值,生成一个新的LIBRARY_PATH(我们手动赋予的LIBRARY_PATH变量的值处于一个特定的位置),并将这个新的LIBRARY_PATH的值(除了/usr/lib/lib)加上-L传递给编译器
  • 我们显式使用-L指定的路径也被传递给编译器,并位于所有-L选项的最前面

而且对于编译器配置的路径,如/usr/lib/gcc/x86_64-linux-gnu/11/../../../../lib/,其本质就是/usr/lib/(这也是默认路径优先级大于LIBRARY_PATH指定路径优先级的原因)。

因此对于-L指定路径LIBRARY_PATH指定路径默认路径,最终都被转化为-L的形式传递给编译器,且他们排列优先级为:

-L指定路径>默认路径>LIBRARY_PATH指定路径

因此他们的搜索优先级也是符合上述排列。

验证

最后我们可以通过链接器在链接特定库(比如我们的libhello)时的搜索过程验证上述结论:
在这里插入图片描述
可见链接器先是搜索我们使用-L指定的路径/opt/hellolib_L,然后搜索编译器配置的路径/usr/lib/gcc/x86_64-linux-gnu/11/../../../../lib/(其本质就是默认路径/usr/lib/),最后搜索LIBRARY_PATH指定的路径/opt/hellolib。证明了编译过程中链接时库搜索路径的优先级为

-L指定路径>默认路径>LIBRARY_PATH指定路径

默认路径>LIBRARY_PATH原因

如上文所述,g++根据自己的配置将例如:

/usr/lib/gcc/x86_64-linux-gnu/11/../../../../lib/

的路径添加到了LIBRARY_PATH中,而且位于用户设置的LIBRARY_PATH之前。这个路径的本质就是/usr/lib/。这就导致最终出现默认路径搜索优先级大于LIBRARY_PATH指定路径的搜索优先级的现象。

至于手动使用ld去链接.o.so文件,后面有机会再做测试。

附录

库文件源码

//file: libhello.cpp
#include <iostream>
void hello()
{
    std::cout << "Hello from the 1.1.0 library!" << std::endl;
}
//file: libhello_alt.cpp
#include <iostream>
void hello()
{
    std::cout << "Hello from the 2.1.0 library!" << std::endl;
}

主程序源码

//file: main.cpp
extern void hello();
int main()
{
    hello();
    return 0;
}

makefile

//file: makefile
CXX = g++
CXXFLAGS = -fPIC
LDFLAGS = -shared
DEBUG_MODE ?= 0

ifeq ($(DEBUG_MODE),1)
    DEBUG_OPTS = -v -Wl,--verbose
endif

all: lib/libhello.so.1.1.0 lib/libhello.so.2.1.0 obj/main.o

lib/libhello.so.1.1.0: libhello.cpp
	$(CXX) $(CXXFLAGS) -o $@ $^ $(LDFLAGS) -Wl,-soname,libhello.so.1

lib/libhello.so.2.1.0: libhello_alt.cpp
	$(CXX) $(CXXFLAGS) -o $@ $^ $(LDFLAGS) -Wl,-soname,libhello.so.2

obj/main.o: main.cpp
	$(CXX) -c -o $@ $^

# 在任何路径下都无法搜索到libhello.so
main_none: obj/main.o
	$(CXX) $(DEBUG_OPTS) -o $@ $^ -lhello

# 测试默认路径/usr/lib 
main_default: obj/main.o
	cp ./lib/libhello.so.1.1.0 /usr/lib
	ln -sf /usr/lib/libhello.so.1.1.0 /usr/lib/libhello.so
	$(CXX) $(DEBUG_OPTS) -o $@ $^ -lhello

# 测试仅使用LIBRARY_PATH
main_library_path: obj/main.o
	mkdir -p /opt/hellolib
	cp ./lib/libhello.so.1.1.0 /opt/hellolib
	ln -sf /opt/hellolib/libhello.so.1.1.0 /opt/hellolib/libhello.so
	LIBRARY_PATH=/opt/hellolib $(CXX) $(DEBUG_OPTS) -o $@ $^ -lhello

# 测试仅使用-L
main_l: obj/main.o
	mkdir -p /opt/hellolib
	cp ./lib/libhello.so.1.1.0 /opt/hellolib
	ln -sf /opt/hellolib/libhello.so.1.1.0 /opt/hellolib/libhello.so
	$(CXX) $(DEBUG_OPTS) -o $@ $^ -L/opt/hellolib -lhello

# 比较默认路径和LIBRARY_PATH的搜索优先级
cmp_default_libpath: obj/main.o
	cp ./lib/libhello.so.2.1.0 /usr/lib
	ln -sf /usr/lib/libhello.so.2.1.0 /usr/lib/libhello.so
	mkdir -p /opt/hellolib
	cp ./lib/libhello.so.1.1.0 /opt/hellolib
	ln -sf /opt/hellolib/libhello.so.1.1.0 /opt/hellolib/libhello.so
	LIBRARY_PATH=/opt/hellolib $(CXX) $(DEBUG_OPTS) -o $@ $^ -lhello

# 比较-L和默认路径的优先级
cmp_l_default: obj/main.o
	mkdir -p /opt/hellolib
	cp ./lib/libhello.so.2.1.0 /opt/hellolib
	ln -sf /opt/hellolib/libhello.so.2.1.0 /opt/hellolib/libhello.so
	cp ./lib/libhello.so.1.1.0 /usr/lib
	ln -sf /usr/lib/libhello.so.1.1.0 /usr/lib/libhello.so
	$(CXX) $(DEBUG_OPTS) -o $@ $^ -L/opt/hellolib -lhello

# 总体比较测试,集合了显示-L,LIBRARY_PATH和默认路径
cmp_all: main.cpp
	mkdir -p /opt/hellolib
	mkdir -p /opt/hellolib_L
	cp ./lib/libhello.so.1.1.0 /opt/hellolib
	ln -sf /opt/hellolib/libhello.so.1.1.0 /opt/hellolib/libhello.so
	LIBRARY_PATH=/opt/hellolib $(CXX) $(DEBUG_OPTS) -o $@ $^ -L/opt/hellolib_L -lhello

clean:
	rm -f ./lib/* ./obj/* main_* cmp_*
	rm -f /usr/lib/libhello.so*
	rm -rf /opt/hellolib*
	ldconfig
	
.PHONY: clean main_none main_default main_library_path  main_l cmp_default_libpath cmp_l_default cmp_all

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

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

相关文章

bugku-web-ctf-变量1

<?php error_reporting(0); include "flag1.php"; highlight_file(__file__); if(isset($_GET[args])){$args $_GET[args];if(!preg_match("/^\w$/",$args)){die("args error!");}eval("var_dump($$args);"); } ?> error_r…

Apache、nginx

一、Web 1、概述 Web&#xff1a;为⽤户提供的⼀种在互联⽹上浏览信息的服务&#xff0c;Web 服务是动态的、可交互的、跨平台的和图形化的。 Web 服务为⽤户提供各种互联⽹服务&#xff0c;这些服务包括信息浏览服务&#xff0c;以及各种交互式服务&#xff0c;包括聊天、购物…

React基础知识 精简全面 推荐

这篇博文主要对一些刚入门react框架的同学&#xff0c;以及对react基本知识进行巩固的&#xff0c;最后就是精简一下基本知识&#xff0c;以方便自己查看&#xff0c;感谢参考&#xff0c;有问题评论区交流&#xff0c;谢谢。 目录 1.JSX 2.Props 和 State 3.组件生命周期…

“八股文”在实际工作中是助力、阻力还是空谈?

程序员面试中的“八股文”&#xff1a;助力、阻力还是空谈&#xff1f; 在当前的技术行业&#xff0c;程序员的招聘面试过程中频繁出现对“八股文”的考核。“八股文”通常指的是关于编程知识的标准化回答&#xff0c;这些问题在网络上大量流传&#xff0c;并被求职者反复背诵…

Socket通信(C++)

文章目录 什么是SocketSocket通信过程C Socket通信APIint socket(int domain, int type, int protocol);int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);struct sockaddrstruct sockaddr_unstruct sockaddr_in / struct sockaddr_in6 int connect(int …

IP Fabric三层路由

IP Fabric指的是在IP网络基础上建立起来的Overlay隧道技术。即为基于胖树的SpineLeaf拓扑结构的IP Fabric组网图。 在这种组网方式中&#xff0c;任何两台服务器间的通信不超过3台设备&#xff0c;每个Spine和Leaf节点全互连&#xff0c;可以方便地通过扩展Spine节点来实现网络…

Godot学习笔记6——数组和for

一、定义一个数组 在Godot中&#xff0c;定义一个数组的关键字也是“var”&#xff0c;数组里面的内容使用方括号括起来。在没有限定类型时&#xff0c;我们可以放入任何类型的数据&#xff1a; 我们甚至可以将另一个数组放入此数组中&#xff1a; 和其他类型的变量类似&#…

【数据结构】包装类泛型

1.包装类 在 Java 中&#xff0c;由于基本类型不是继承自 Object &#xff0c;为了在泛型代码中可以支持基本类型&#xff0c; Java 给每个基本类型都对应了 一个包装类型。 1.1.基本的数据类型对应的包装类 1.2装箱和拆箱 //装箱int a10;Integer cInteger.valueOf(a);System.…

鸿蒙应用框架开发【简单时钟】 UI框架

简单时钟 介绍 本示例通过使用ohos.display接口以及Canvas组件来实现一个简单的时钟应用。 效果预览 使用说明 1.界面通过setInterval实现周期性实时刷新时间&#xff0c;使用Canvas绘制时钟&#xff0c;指针旋转角度通过计算得出。 例如&#xff1a;"2 * Math.PI / …

Synchronized的锁升级过程是怎样的?

文章目录 一、Synchronized的使用1、修饰实例方法2、修饰静态方法3、修饰代码块4、总结&#xff1a; 二、Monitor1、Java对象头1.1 32 位虚拟机的对象头1.2 64位虚拟机的对象头 2、Mark Word 结构3、Moniter4、Synchronized 字节码5、轻量级锁6、锁膨胀7、自旋优化8、偏向锁9、…

Python for循环迭代原理(迭代器 Iterator)

在使用Python时&#xff0c;我们经常会使用for循环来访问容器对象&#xff08;列表、字符、字典等&#xff09;中的元素。其幕后实际是通过迭代协议来完成的&#xff0c;迭代是一种依次访问对象中元素的方式&#xff0c;for循环在对象上调用iter()函数生成一个迭代器&#xff0…

从后端开发视角认识向量数据库

以ChatGPT为代表的大语言模型应用自问世以来已经火了好几年。在这期间国内外类似产品层出不穷&#xff0c;甚至公司内部团队都开发了好几个AI小助手。刚好最近看了几篇关于大语言模型应用开发的文章&#xff0c;借此了解了一下应用层面的基本知识&#xff0c;也算是接触到了大语…

轻松入门Linux—CentOS,直接拿捏 —/— <2>

一 、权限问题详细讲解 读写的权限可以分别写成 r, w, x 总共有九个权限&#xff0c;可以分组三大组分别是&#xff1a; user&#xff1a;当前文件所属用户的权限 group&#xff1a;与当前文件所属用户同一组的用户权限 others&#xff1a;其他用户的权限 故使用 u, g, o 来代表…

Qt Creator 与 ESP-IDF QEMU 模拟器使用指南

标题: Qt Creator 与 ESP-IDF QEMU 模拟器使用指南 概要: 本文为开发者提供了使用 Qt Creator 和 ESP-IDF QEMU 模拟器进行 ESP32 开发的详细指南&#xff0c;包括环境准备、项目创建和编译、模拟器设置、编程和调试等方面的内容。通过本指南&#xff0c;可以快速上手 Qt Crea…

bean管理

获取bean bean作用域 第三方bean

如何使用虚拟机如何安装 Kali Linux ?

1.下载虚拟机&#xff1a;https://www.virtualbox.org/wiki/Downloads 选择你的系统版本 2.下载kali linux系统镜像&#xff1a;https://www.kali.org/get-kali/#kali-virtual-machines 全部下载完成后&#xff0c;我们会得到以下文件&#xff01; 1.压缩Kali Linux压缩包 2.安…

OpenCV C++的网络实时视频流传输——基于Yolov5 face与TCP实现实时推流的深度学习图像处理客户端与服务器端

前言 在Windows下使用TCP协议&#xff0c;基于OpenCV C与Yolov5实现了一个完整的实时推流的深度学习图像处理客户端与服务器端&#xff0c;为了达到实时传输的效果&#xff0c;客户端使用了多线程的方式实现。深度学习模型是基于onnxruntime的GPU推理。&#xff0c;实现效果如…

跨境电商网红营销SOP流程2.0丨出海笔记

之前几位大神已经在出海笔记分享过网红营销一些很落地的干货&#xff0c;无论是想自己找红人还是找Agency都很有必要了解下这里面的流程的&#xff0c;下面我大概总结了一个SOP2.0 供大家快速上手&#xff1a; 以上是网红营销的SOP&#xff0c;做到以上部分基本60分没问题了…

【云原生】Kubernetes中crictl的详细用法教程与应用实战

✨✨ 欢迎大家来到景天科技苑✨✨ &#x1f388;&#x1f388; 养成好习惯&#xff0c;先赞后看哦~&#x1f388;&#x1f388; &#x1f3c6; 作者简介&#xff1a;景天科技苑 &#x1f3c6;《头衔》&#xff1a;大厂架构师&#xff0c;华为云开发者社区专家博主&#xff0c;…

AI作图接口要怎么调用呢?

一、什么是AI作图&#xff1f; 基于AI大模型的深度学习算法和大规模的图像数据训练&#xff0c;输入图片和关键词&#xff0c;可生成独特及富有创意的山水风格图片。 二、AI作图使用场景有哪些呢&#xff1f; 1.广告与营销&#xff1a; 为产品制作吸引人的宣传海报、广告图片…