溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

自動(dòng)生成依賴關(guān)系(十)

發(fā)布時(shí)間:2020-06-05 07:39:19 來(lái)源:網(wǎng)絡(luò) 閱讀:817 作者:上帝之子521 欄目:系統(tǒng)運(yùn)維

        我們?cè)谥暗?makefile 學(xué)習(xí)中,其目標(biāo)文件(.o)只依賴于源文件(.c)。那么如果在源文件中還包含有頭文件,此時(shí)編譯器如何編譯源文件和頭文件呢?我們來(lái)看看編譯行為帶來(lái)的缺陷:1、預(yù)處理器將頭文件中的代碼直接插入源文件;2、編譯器只通過(guò)預(yù)處理后的源文件產(chǎn)生目標(biāo)文件;3、規(guī)則中以源文件為依賴,命令就可能無(wú)法執(zhí)行。

        我們來(lái)看看下面的 makefile 有沒有問(wèn)題


makefile 源碼

OBJS := func.o main.o

hello.out : $(OBJS)
    @gcc -o $@ $^
    @echo "Target File ==> $@"

$(OBJS) : %.o : %.c
    @gcc -o $@ -c $^


func.h 源碼

#ifndef _FUNC_H_
#define _FUNC_H_

#define HELLO "Hello D.T."

void foo();

#endif


func.c 源碼

#include <stdio.h>
#include "func.h"

void foo()
{
    printf("void foo() : %s\n", HELLO);
}


main.c 源碼

#include <stdio.h>
#include "func.h"

int main()
{
    foo();

    return 0;
}

        我們來(lái)看看編譯結(jié)果

自動(dòng)生成依賴關(guān)系(十)

        我們看到已經(jīng)正確實(shí)現(xiàn)了字符串的打印,那么我們接下來(lái)在 func.h 源文件中想要改掉這個(gè)字符串為 Software 呢?試試看能不能修改成功

自動(dòng)生成依賴關(guān)系(十)

        我們看到在重新編譯的時(shí)候,它并沒有因?yàn)轭^文件的改變而改變,我們?cè)?makefile 中又沒有進(jìn)行頭文件的相關(guān)添加,改掉頭文件中的內(nèi)容肯定是不動(dòng)的。下來(lái)我們?cè)谀J揭?guī)則中加上頭文件,在 %.c 后加上 func.h,再來(lái)看看編譯結(jié)果

自動(dòng)生成依賴關(guān)系(十)

        我們看到直接添加之后,編譯出錯(cuò)了。因?yàn)?-c 后面的目標(biāo)中含有頭文件,所以不能直接進(jìn)行編譯。我們可以只編譯 %.o 后面的第一依賴 %.c,這樣就不會(huì)去編譯 func.h 頭文件了,將下面的 $^ 改為 $< ,我們來(lái)看看效果

自動(dòng)生成依賴關(guān)系(十)

        我們看到已經(jīng)正確改過(guò)來(lái)了。經(jīng)過(guò)上面的實(shí)驗(yàn),我們看到:頭文件作為依賴條件出現(xiàn)于每個(gè)目標(biāo)對(duì)應(yīng)的規(guī)則中,當(dāng)頭文件改動(dòng)時(shí),任何源文件都將會(huì)被重新編譯(編譯低效);當(dāng)項(xiàng)目中頭文件巨大時(shí),makefile 將很難維護(hù)。那么我們的頭腦中不禁會(huì)冒出這么個(gè)想法:通過(guò)命令對(duì)自動(dòng)生成對(duì)頭文件的依賴;將生成的依賴自動(dòng)包含進(jìn) makefile 中;當(dāng)頭文件改動(dòng)后,自動(dòng)確認(rèn)需要重新編譯的文件。那么此時(shí)我們還需要知道一個(gè)命令,Linux 中的 sed 命令。sed 是一個(gè)流編輯器,用于流文本的修改(增、刪、查、改);它可用于流文本中的字符串替換,其字符串替換方式為:sed 's:src:des:g',具體格式如下

自動(dòng)生成依賴關(guān)系(十)

        sed 同樣也支持正則表達(dá)式,在 sed 中可以用正則表達(dá)式匹配替換目標(biāo),并且可以使用匹配的目標(biāo)生成替換結(jié)果。格式如下

自動(dòng)生成依賴關(guān)系(十)

        下來(lái)我們以代碼為例來(lái)看看 sed 命令是如何使用的

自動(dòng)生成依賴關(guān)系(十)

        再來(lái)看看 gcc 關(guān)鍵編譯選項(xiàng),獲取目標(biāo)的完整依賴關(guān)系:gcc -M test.c;獲取目標(biāo)的部分依賴關(guān)系:gcc -MM test.c。makefile 如下

.PHONY : test

test :
    gcc -M main.c

        編譯結(jié)果如下

自動(dòng)生成依賴關(guān)系(十)

        我們看到 -M 是獲取了它的所有依賴關(guān)系,再來(lái)試試 -MM 呢

自動(dòng)生成依賴關(guān)系(十)

        我們看到 -MM 后,它只依賴與 main.c func.h。我們可以拆分目標(biāo)的依賴,即將目標(biāo)的完整依賴差分為多個(gè)部分依賴。格式如下

自動(dòng)生成依賴關(guān)系(十)

        我們來(lái)做個(gè)實(shí)驗(yàn)

.PHONY : a b c

test : a b

test : b c

test :
    @echo "$^"

        我們來(lái)打印看看目標(biāo) test 的依賴都有哪些,編譯結(jié)果如下

自動(dòng)生成依賴關(guān)系(十)

        那么我們思考下:如何將 sed 和 gcc -MM 用于 makefile,并自動(dòng)生成依賴關(guān)系呢?

        我們?cè)賮?lái)看看 makefile 中的 include 關(guān)鍵字,它類似于 C 語(yǔ)言中的 include,是將其它文件的內(nèi)容原封不動(dòng)的搬入當(dāng)前文件。make 對(duì) include 關(guān)鍵字的處理方式是在當(dāng)前目錄下搜索或指定搜索目標(biāo)文件。如果搜索一成功,便將文件內(nèi)容搬入當(dāng)前 makefile 中;如果搜索失敗,將會(huì)產(chǎn)生警告,以文件名作為目標(biāo)查找并執(zhí)行對(duì)應(yīng)規(guī)則,當(dāng)文件名對(duì)應(yīng)的規(guī)則不存在時(shí),最終產(chǎn)生錯(cuò)誤。格式如下

自動(dòng)生成依賴關(guān)系(十)

        下來(lái)還是以代碼為例來(lái)進(jìn)行說(shuō)明

.PHONY : test

include test.txt

all :
    @echo "this is $@"

test.txt :
    @echo "test.txt"
    @touch test.txt

        我們?cè)诘?3 行包含 test.txt,可是當(dāng)前目錄下并沒有 test.txt,然后觸發(fā) test.txt 的規(guī)則。因而會(huì)打印出 test.txt,然后再創(chuàng)建 test.txt,我們來(lái)看看編譯結(jié)果

自動(dòng)生成依賴關(guān)系(十)

        我們看到確實(shí)是創(chuàng)建了一個(gè) test.txt 文件。那么在 makefile 中命令的執(zhí)行是:1、規(guī)則中的每個(gè)命令默認(rèn)是在一個(gè)新的進(jìn)程中執(zhí)行(Shell);2、可以通過(guò)接續(xù)符(;)將多個(gè)命令組合成一個(gè)命令;3、組合的命令依次在同一個(gè)進(jìn)程中被執(zhí)行;4、set -e 指定發(fā)生錯(cuò)誤后立即退出執(zhí)行。那么我們看看下面的代碼會(huì)實(shí)現(xiàn)想要的功能嗎?

.POHONY : all

all :
    mkdir test
    cd test
    mkdir subtest

        我們來(lái)看看編譯結(jié)果自動(dòng)生成依賴關(guān)系(十)

        我們看到在當(dāng)前目錄下創(chuàng)建了目錄,但是 subtest 目錄卻不是在 test 目錄下創(chuàng)建的,這是怎么回事呢?在第一條命令執(zhí)行時(shí)創(chuàng)建了目錄 test,此時(shí)這個(gè)進(jìn)程已經(jīng)關(guān)閉了;在第二條命令執(zhí)行時(shí),執(zhí)行的是另一個(gè)進(jìn)程,雖然它已經(jīng)進(jìn)入到目錄 test 中,但是隨著這個(gè)進(jìn)程的關(guān)閉,又回到了當(dāng)前目錄;第三個(gè)進(jìn)程是重新創(chuàng)建了目錄 subtest。那么如何解決這個(gè)問(wèn)題呢?直接利用 set -e 和 接續(xù)符來(lái)解決

.PHONY : test

all :
    set -e; \
    mkdir test; \
    cd test; \
    mkdir subtest

        看看編譯結(jié)果

自動(dòng)生成依賴關(guān)系(十)

        那么我們之前思考問(wèn)題的初步思路是:1、通過(guò) gcc -MM 和 sed 得到 .dep 依賴文件(目標(biāo)的部分依賴),技術(shù)點(diǎn)是規(guī)則中命令的連續(xù)執(zhí)行;2、通過(guò) include 指令包含所有的 .dep 依賴文件。技術(shù)點(diǎn)是當(dāng) .dep 依賴文件不存在時(shí),使用規(guī)則自動(dòng)生成。下面我們來(lái)看看解決方案是怎樣的

ONY : all clean

MKDIR := mkdir
RM := rm -fr
CC := gcc

SRCS := $(wildcard *.c)
DEPS := $(SRCS:.c=.dep)

include $(DEPS)

all :
    @echo "all"
        
%.dep : %.c
    @echo "Creating $@ ..."
    @set -e; \
    $(CC) -MM -E $^ | sed 's,\(.*\)\.o[ :]*,objs/\1.o : ,g' > $@

clean :
    $(RM) $(DEPS)

        我們來(lái)看看編譯結(jié)果

自動(dòng)生成依賴關(guān)系(十)

        我們先來(lái)分析下,在執(zhí)行 make all 前,它先通過(guò) include 包含 $(DEPS),通過(guò) $(DEPS) 觸發(fā)模式規(guī)則,進(jìn)而創(chuàng)建文件夾。我們看到在前面出現(xiàn)兩個(gè)沒有文件夾的信息,其實(shí)這條信息是可以隱藏的。我們?cè)?include 前面加上 - 就 OK,來(lái)看看效果

自動(dòng)生成依賴關(guān)系(十)

        我們看到并沒打印出前面的兩條信息了。那么我們?cè)賮?lái)思考下:如何組織依賴文件相關(guān)的規(guī)則與源碼編譯相關(guān)的規(guī)則,進(jìn)而形成功能完整的 makefile  程序呢?我們?nèi)绾卧?makefile 中組織 .dep 文件到指定目錄呢?初步想法是當(dāng) include 發(fā)現(xiàn) .dep 文件不存在時(shí):1、通過(guò)規(guī)則和命令創(chuàng)建 deps 文件;2、將所有 .dep 文件創(chuàng)建到 deps 文件夾;3、.dep 文件中記錄目標(biāo)文件的依賴關(guān)系

        我們下來(lái)看看初步的代碼設(shè)計(jì)是怎樣的

.PHONY : all clean

MKDIR := mkdir
RM := rm -rf
CC := gcc

DIR_DEPS := deps

SRCS := $(wildcard *.c)
DEPS := $(SRCS:.c=.dep)
DEPS := $(addprefix $(DIR_DEPS)/, $(DEPS))

include $(DEPS)

all :
    @echo "all"

$(DIR_DEPS) :
    $(MKDIR) $@

$(DIR_DEPS)/%.dep : $(DIR_DEPS) %.c
    @echo "Creating $@ ..."
    @set -e; \
    $(CC) -MM -E $(filter %.c, $^) | sed 's,\(.*\)\.o[ :]*,objs/\1.o : ,g' > $@

clean :
    $(RM) $(DIR_DEPS)

         我們來(lái)看看編譯結(jié)果,是不是都將所有的 .dep 文件放入一個(gè) deps 文件中

自動(dòng)生成依賴關(guān)系(十)

        我們看到已經(jīng)實(shí)現(xiàn)效果了。我們仔細(xì)看看 make 有一個(gè)警告,說(shuō) main.dep 被修改了,也就是說(shuō) main.dep 被重新創(chuàng)建了。那么我們來(lái)分析下,為什么一些 .dep 依賴文件會(huì)被重復(fù)創(chuàng)建多次呢?deps 文件夾的時(shí)間屬性會(huì)因?yàn)橐蕾囄募?chuàng)建而發(fā)生改變,make 發(fā)現(xiàn) deps 文件夾比對(duì)應(yīng)的目標(biāo)更新,于是乎就觸發(fā)相應(yīng)的規(guī)則重新解析和執(zhí)行命令。那么我們知道了原因,此時(shí)這個(gè)方案該如何優(yōu)化呢?我們可以使用 ifeq 動(dòng)態(tài)決定 .dep 目標(biāo)的依賴,具體 makefile 如下

.PHONY : all clean

MKDIR := mkdir
RM := rm -fr
CC := gcc

DIR_DEPS := deps

SRCS := $(wildcard *.c)
DEPS := $(SRCS:.c=.dep)
DEPS := $(addprefix $(DIR_DEPS)/, $(DEPS))


all : 
    @echo "all"

ifeq ("$(MAKECMDGOALS)", "all")
-include $(DEPS)
endif

ifeq ("$(MAKECMDGOALS)", "")
-include $(DEPS)
endif

$(DIR_DEPS) :
    $(MKDIR) $@

ifeq ("$(wildcard $(DIR_DEPS))", "")
$(DIR_DEPS)/%.dep : $(DIR_DEPS) %.c
else
$(DIR_DEPS)/%.dep : %.c
endif
    @echo "Creating $@ ..."
    @set -e; \
    $(CC) -MM -E $(filter %.c, $^) | sed 's,\(.*\)\.o[ :]*,objs/\1.o : ,g' > $@
    
clean :
    $(RM) $(DIR_DEPS)

        我們?cè)俅尉幾g看看

自動(dòng)生成依賴關(guān)系(十)

        我們看到它還是報(bào)了這樣的錯(cuò)誤,有可能是編譯器的優(yōu)化造成的。思路是正確的。下來(lái)我們來(lái)看看 include 的一些鮮為人知的秘密。

        A、 使用減號(hào)(-)不但關(guān)閉了 include 發(fā)出的警告,同時(shí)將關(guān)閉了錯(cuò)誤;當(dāng)錯(cuò)誤發(fā)生時(shí) make 將忽略這些錯(cuò)誤! 以代碼為例來(lái)進(jìn)行分析說(shuō)明

.PHONY : all

include test.txt

all :
    @echo "this is all"

test :
    @echo "creating $@ ..."
    @echo "other : ; @echo "this is other" " > test.txt

        我們來(lái)編譯看看

自動(dòng)生成依賴關(guān)系(十)

        我們看到不但發(fā)出警告,而且報(bào)錯(cuò)了。下來(lái)我們來(lái)在 include 前面加上 - 試試

自動(dòng)生成依賴關(guān)系(十)

        這樣它也不報(bào)錯(cuò)了,直接就通過(guò)了,我們還以為 makefile 寫的對(duì)著呢。這便是第一個(gè)暗黑操作。下來(lái)看看第二個(gè)暗黑操作

        B、如果 include 觸發(fā)規(guī)則創(chuàng)建了文件,之后還會(huì)發(fā)生什么?以代碼為例來(lái)進(jìn)行分析說(shuō)明

.PHONY : all

include test.txt

all :
    @echo "this is all"

test.txt :
    @echo "creating $@ ..."
    @echo "other : ; @echo "this is other" " > test.txt

        看看編譯結(jié)果

自動(dòng)生成依賴關(guān)系(十)

        我們進(jìn)行直接 make 的時(shí)候,發(fā)現(xiàn)它輸出的 this is other,并不是我們所期望的 this is all。這是為什么呢?因?yàn)樵?include 的時(shí)候,直接將 test.txt 鋪開在這,此時(shí)會(huì)觸發(fā)規(guī)則。makefile 就變成了下面這樣

.PHONY : all

other : 
    @echo "creating $@ ..."
    @echo "this is other"

all :
    @echo "this is all"

        我們?cè)谥苯?make 的時(shí)候,它默認(rèn)執(zhí)行的是第一個(gè)目標(biāo),因此便會(huì)輸出 this is other,只有當(dāng)我們 make all 的時(shí)候才會(huì)輸出 this is all。這便是 include 的第二個(gè)暗黑操作了,下面繼續(xù)看看第三個(gè)

        C、如果 include 包含的文件存在,之后會(huì)發(fā)生什么呢?以代碼為例來(lái)進(jìn)行分析說(shuō)明

.PHONY : all

-include test.txt

all :
    @echo "this is all"

test.txt : b.txt
    @echo "this is $@"

        在當(dāng)前目錄下創(chuàng)建一個(gè) b.txt 文件,看看編譯結(jié)果

自動(dòng)生成依賴關(guān)系(十)

        我們看到同樣也執(zhí)行了 test.txt 的相應(yīng)的規(guī)則??纯聪旅孢@個(gè) makefile 將會(huì)輸出什么

.PHONY : all

-include test.txt

all : 
    @echo "$@ : $^"
    
test.txt : b.txt
    @echo "creating $@ ..."
    @echo "all : c.txt" > test.txt

        看看結(jié)果

自動(dòng)生成依賴關(guān)系(十)

        我們看到它最后輸出的 all 的依賴是 c.txt,不應(yīng)該覺得奇怪嗎?我們明明在 all 后面沒有依賴啊。再來(lái)看看生成的 test.txt 文件,它的內(nèi)容是 all : c.txt,因此輸出的結(jié)果是我們意想不到的。那么我們關(guān)于 include 便有了這幾條總結(jié):1、當(dāng)目標(biāo)文件不存在時(shí),以文件名查找規(guī)則并執(zhí)行;2、當(dāng)目標(biāo)文件不存在時(shí)且查找到的規(guī)則中創(chuàng)建了目標(biāo)文件,將創(chuàng)建成功的目標(biāo)文件包含進(jìn)當(dāng)前的 makefile 中;3、當(dāng)目標(biāo)文件存在,將目標(biāo)文件包含進(jìn)當(dāng)前 makefile,以目標(biāo)文件名查找是否有相應(yīng)規(guī)則,YES 的話則比較規(guī)則的依賴關(guān)系來(lái)決定是否執(zhí)行規(guī)則的命令,NO 的話則 NULL(無(wú)操作)。4、當(dāng)目標(biāo)文件存在且目標(biāo)名對(duì)應(yīng)的規(guī)則被執(zhí)行,規(guī)則中的命令更新了目標(biāo)文件,make 重新包含目標(biāo)文件,替換之前包含的內(nèi)容。目標(biāo)文件未被更新,便是 NULL(無(wú)操作)。

        經(jīng)過(guò)了這么多的知識(shí)點(diǎn)的探索,此時(shí)已經(jīng)具備實(shí)現(xiàn)之前的想法的能力了。想要實(shí)現(xiàn)的具體格式如下

自動(dòng)生成依賴關(guān)系(十)

        下面我們就根據(jù)這個(gè)來(lái)編寫相關(guān)的 makefile。

func.h 源碼

#ifndef FUNC_H
#define FUNC_H

#define HELLO "hello Makefile"

#endif


func.c 源碼

#include <stdio.h>
#include "func.h"

void foo()
{
    printf("void foo() : %s\n", HELLO);
}


main.c 源碼

#include <stdio.h>
#include "func.h"

int main()
{
    foo();
    
    return 0;
}


makefile 源碼

.PHONY : all clean

MKDIR := mkdir
RM := rm -rf
CC := gcc

DIR_DEPS := deps
DIR_OBJS := objs
DIR_EXES := exes

DIRS := $(DIR_DEPS) $(DIR_EXES) $(DIR_OBJS)

EXE := app.out
EXE := $(addprefix $(DIR_EXES)/, $(EXE))

SRCS := $(wildcard *.c)
OBJS := $(SRCS:.c=.o)
OBJS := $(addprefix $(DIR_OBJS)/, $(OBJS))
DEPS := $(SRCS:.c=.dep)
DEPS := $(addprefix $(DIR_DEPS)/, $(DEPS))

all : $(DIR_OBJS) $(DIR_EXES) $(EXE)

ifeq ("$(MAKECMDGOALS)", "all")
-include $(DEPS)
endif

ifeq ("$(MAKECMDGOALS)", "")
-include $(DEPS)
endif

$(EXE) : $(OBJS)
    $(CC) -o $@ $^
    @echo "Success! Target => $@"

$(DIR_OBJS)/%.o : %.c
    $(CC) -o $@ -c $^

$(DIRS) :
    $(MKDIR) $@

ifeq ("$(wildcard $(DIR_DEPS))", "")
$(DIR_DEPS)/%.dep : $(DIR_DEPS) %.c
else
$(DIR_DEPS)/%.dep : %.c
endif
    @echo "Creating $@ ..."
    @set -e; \
    $(CC) -MM -E $(filter %.c, $^) | sed 's,\(.*\)\.o[ :]*,objs/\1.o  $@ : ,g' > $@
                
clean :
    $(RM) $(DIRS)

        編譯結(jié)果如下

自動(dòng)生成依賴關(guān)系(十)

        我們看到已經(jīng)自動(dòng)生成了,并且最后的結(jié)果也是我們想要的,那么我們?nèi)绻?func.h 中改變字符串,看看結(jié)果是否也會(huì)改變

自動(dòng)生成依賴關(guān)系(十)

        我們看到在編譯的時(shí)候報(bào)錯(cuò)了,原因是只能編譯 .c 文件,.h 頭文件不參與編譯,這時(shí)我們便要用到預(yù)定義函數(shù) filter 了。因此我們需要在 makefile 第37 行將它改為 $(CC) -o $@ -c $(filter %.c, $^);再來(lái)看看效果

自動(dòng)生成依賴關(guān)系(十)

        我們看到也成功的替換掉了。這時(shí)我們基本上已經(jīng)完成我們之前的想法了,那么在實(shí)際開發(fā)中,肯定需要時(shí)不時(shí)的添加頭文件,我們?cè)賮?lái)在 func.h 中包含一個(gè)頭文件 define.h,在 define.h 文件中定義字符串 hello-makefile,看看結(jié)果是否會(huì)跟著改變

自動(dòng)生成依賴關(guān)系(十)

        我們看到字符串并沒有發(fā)生改變,再來(lái)看看 func.dep 和 main.dep 中是否包含了 define.h

自動(dòng)生成依賴關(guān)系(十)

        也沒有包含,按理說(shuō)不應(yīng)該,因?yàn)槲覀冊(cè)?func.h 中包含了 define.h,那么在 func.c 和 main.c 中肯定也就包含了 define.h。下來(lái)我們來(lái)分析下這個(gè),當(dāng) .dep 文件生成后,如果動(dòng)態(tài)的改變頭文件間的依賴關(guān)系,那么 make 可能無(wú)法檢測(cè)到這個(gè)改變,進(jìn)而做出錯(cuò)誤的編譯決策。解決方案便是:1、將依賴文件名作為目標(biāo)加入自動(dòng)生成的依賴關(guān)系中;2、通過(guò) include 加載依賴文件時(shí)判斷是否執(zhí)行規(guī)則;3、在規(guī)則執(zhí)行時(shí)重新生成依賴關(guān)系文件;4、最后加載新的依賴文件。解決方法是在 sed 命令后加上 $@,看看編譯效果,順便我們?cè)賮?lái)加上 rebuild。

自動(dòng)生成依賴關(guān)系(十)

        我們看到已經(jīng)正確實(shí)現(xiàn)了,我們來(lái)看看在 deps 文件下的 .dep 文件是否包含 define.h 呢?

自動(dòng)生成依賴關(guān)系(十)

        確實(shí)是包含了 define.h,我們?cè)賮?lái)加上 new.h,看看是否還會(huì)有效

自動(dòng)生成依賴關(guān)系(十)

        我們看到 new.h 同樣也包含進(jìn)去了。通過(guò)對(duì)綜合示例的學(xué)習(xí),總結(jié)如下:1、makefile 中可以將目標(biāo)的依賴拆分寫到不同的地方;2、include 關(guān)鍵字能夠觸發(fā)相應(yīng)規(guī)則的執(zhí)行;3、如果規(guī)則的執(zhí)行導(dǎo)致依賴更新,可能導(dǎo)致再次解釋執(zhí)行相應(yīng)規(guī)則;4、依賴文件也需要依賴于源文件得到正確的編譯決策;5、自動(dòng)生成文件間的依賴關(guān)系能夠提高 makefile 的移植性。


        歡迎大家一起來(lái)學(xué)習(xí) makefile,可以加我QQ:243343083

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI