![詳解-C語言可變參數(shù)-va-list和-vsnprintf及printf實(shí)現(xiàn)_第1頁](http://file1.renrendoc.com/fileroot_temp2/2020-11/18/87cbbca9-1e3d-400e-9d18-56fff863b580/87cbbca9-1e3d-400e-9d18-56fff863b5801.gif)
![詳解-C語言可變參數(shù)-va-list和-vsnprintf及printf實(shí)現(xiàn)_第2頁](http://file1.renrendoc.com/fileroot_temp2/2020-11/18/87cbbca9-1e3d-400e-9d18-56fff863b580/87cbbca9-1e3d-400e-9d18-56fff863b5802.gif)
![詳解-C語言可變參數(shù)-va-list和-vsnprintf及printf實(shí)現(xiàn)_第3頁](http://file1.renrendoc.com/fileroot_temp2/2020-11/18/87cbbca9-1e3d-400e-9d18-56fff863b580/87cbbca9-1e3d-400e-9d18-56fff863b5803.gif)
![詳解-C語言可變參數(shù)-va-list和-vsnprintf及printf實(shí)現(xiàn)_第4頁](http://file1.renrendoc.com/fileroot_temp2/2020-11/18/87cbbca9-1e3d-400e-9d18-56fff863b580/87cbbca9-1e3d-400e-9d18-56fff863b5804.gif)
![詳解-C語言可變參數(shù)-va-list和-vsnprintf及printf實(shí)現(xiàn)_第5頁](http://file1.renrendoc.com/fileroot_temp2/2020-11/18/87cbbca9-1e3d-400e-9d18-56fff863b580/87cbbca9-1e3d-400e-9d18-56fff863b5805.gif)
版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請(qǐng)進(jìn)行舉報(bào)或認(rèn)領(lǐng)
文檔簡介
1、C語言的變長參數(shù)在平時(shí)做開發(fā)時(shí)很少會(huì)在自己設(shè)計(jì)的接口中用到,但我們最常用的接口printf就是使用的變長參數(shù)接口,在感受到printf強(qiáng)大的魅力的同時(shí),是否想挖據(jù)一下到底printf是如何實(shí)現(xiàn)的呢?這里我們一起來挖掘一下C語言變長參數(shù)的奧秘。先考慮這樣一個(gè)問題:如果我們不使用C標(biāo)準(zhǔn)庫(libc)中提供的Facilities,我們自己是否可以實(shí)現(xiàn)擁有變長參數(shù)的函數(shù)呢?我們不妨試試。一步一步進(jìn)入正題,我們先看看固定參數(shù)列表函數(shù),void fixed_args_func(int a, double b, char *c) printf(a = 0x%pn, &a); printf(b = 0x%pn
2、, &b); printf(c = 0x%pn, &c);對(duì)于固定參數(shù)列表的函數(shù),每個(gè)參數(shù)的名稱、類型都是直接可見的,他們的地址也都是可以直接得到的,比如:通過&a我們可以得到a的地址,并通過函數(shù)原型聲明了解到a是int類型的; 通過&b我們可以得到b的地址,并通過函數(shù)原型聲明了解到b是double類型的; 通過&c我們可以得到c的地址,并通過函數(shù)原型聲明了解到c是char*類型的。但是對(duì)于變長參數(shù)的函數(shù),我們就沒有這么順利了。還好,按照C標(biāo)準(zhǔn)的說明,支持變長參數(shù)的函數(shù)在原型聲明中,必須有至少一個(gè)最左固定參數(shù)(這一點(diǎn)與傳統(tǒng)C有區(qū)別,傳統(tǒng)C允許不帶任何固定參數(shù)的純變長參數(shù)函數(shù)),這樣我們可以得到
3、其中固定參數(shù)的地址,但是依然無法從聲明中得到其他變長參數(shù)的地址,比如:void var_args_func(const char * fmt, . ) . . 這里我們只能得到fmt這固定參數(shù)的地址,僅從函數(shù)原型我們是無法確定.中有幾個(gè)參數(shù)、參數(shù)都是什么類型的,自然也就無法確定其位置了。那么如何可以做到呢?在大腦中回想一下函數(shù)傳參的過程,無論.中有多少個(gè)參數(shù)、每個(gè)參數(shù)是什么類型的,它們都和固定參數(shù)的傳參過程是一樣的,簡單來講都是棧操作,而棧這個(gè)東西對(duì)我們是開放的。這樣一來,一旦我們知道某函數(shù)幀的棧上的一個(gè)固定參數(shù)的位置,我們完全有可能推導(dǎo)出其他變長參數(shù)的位置,順著這個(gè)思路,我們繼續(xù)往下走,通過
4、一個(gè)例子來詮釋一下:(這里要說明的是:函數(shù)參數(shù)進(jìn)棧以及參數(shù)空間地址分配都是實(shí)現(xiàn)相關(guān)的,不同平臺(tái)、不同編譯器都可能不同,所以下面的例子僅在IA-32,Windows XP, MinGW gcc v3.4.2下成立)我們先用上面的那個(gè)fixed_args_func函數(shù)確定一下這個(gè)平臺(tái)下的入棧順序。int main() fixed_args_func(17, 5.40, hello world); return 0;a = 0x0022FF50b = 0x0022FF54c = 0x0022FF5C從這個(gè)結(jié)果來看,顯然參數(shù)是從右到左,逐一壓入棧中的(棧的延伸方向是從高地址到低地址,棧底的占領(lǐng)著最高內(nèi)
5、存地址,先入棧的參數(shù),其地理位置也就最高了)。我們基本可以得出這樣一個(gè)結(jié)論:c.addr = b.addr + x_sizeof(b); /*注意: x_sizeof != sizeof,后話再說 */b.addr = a.addr + x_sizeof(a);有了以上的等式,我們似乎可以推導(dǎo)出 void var_args_func(const char * fmt, . ) 函數(shù)中,可變參數(shù)的位置了。起碼第一個(gè)可變參數(shù)的位置應(yīng)該是:first_vararg.addr = fmt.addr + x_sizeof(fmt); 根據(jù)這一結(jié)論我們?cè)囍鴮?shí)現(xiàn)一個(gè)支持可變參數(shù)的函數(shù):void var_ar
6、gs_func(const char * fmt, . ) char *ap; ap = (char*)&fmt) + sizeof(fmt); printf(%dn, *(int*)ap); ap = ap + sizeof(int); printf(%dn, *(int*)ap); ap = ap + sizeof(int); printf(%sn, *(char*)ap);int main() var_args_func(%d %d %sn, 4, 5, hello world);輸出結(jié)果:45hello worldvar_args_func只是為了演示,并未根據(jù)fmt消息中的格式字符串
7、來判斷變參的個(gè)數(shù)和類型,而是直接在實(shí)現(xiàn)中寫死了,如果你把這個(gè)程序拿到solaris 9下,運(yùn)行后,一定得不到正確的結(jié)果,為什么呢,后續(xù)再說。先來解釋一下這個(gè)程序。我們用ap獲取第一個(gè)變參的地址,我們知道第一個(gè)變參是4,一個(gè)int型,所以我們用(int*)ap以告訴編譯器,以ap為首地址的那塊內(nèi)存我們要將之視為一個(gè)整型來使用,*(int*)ap獲得該參數(shù)的值;接下來的變參是5,又一個(gè)int型,其地址是ap + sizeof(第一個(gè)變參),也就是ap + sizeof(int),同樣我們使用*(int*)ap獲得該參數(shù)的值;最后的一個(gè)參數(shù)是一個(gè)字符串,也就是char*,與前兩個(gè)int型參數(shù)不同的是
8、,經(jīng)過ap + sizeof(int)后,ap指向棧上一個(gè)char*類型的內(nèi)存塊(我們暫且稱之tmp_ptr, char *tmp_ptr)的首地址,即ap - &tmp_ptr,而我們要輸出的不是printf(%sn, ap),而是printf(%sn, tmp_ptr); printf(%sn, ap)是意圖將ap所指的內(nèi)存塊作為字符串輸出了,但是ap - &tmp_ptr,tmp_ptr所占據(jù)的4個(gè)字節(jié)顯然不是字符串,而是一個(gè)地址。如何讓&tmp_ptr是char *類型的,我們將ap進(jìn)行強(qiáng)制轉(zhuǎn)換(char*)ap &tmp_ptr,這樣我們?cè)L問tmp_ptr只需要在(char*)ap前
9、面加上一個(gè)*即可,即printf(%sn, *(char*)ap);前面說過,如果將var_args_func放到solaris上,一定是得不到正確結(jié)果的?為什么呢?由于內(nèi)存對(duì)齊。編譯器在棧上壓入?yún)?shù)時(shí),不是一個(gè)緊挨著另一個(gè)的,編譯器會(huì)根據(jù)變參的類型將其放到滿足類型對(duì)齊的地址上的,這樣棧上參數(shù)之間實(shí)際上可能會(huì)是有空隙的。上述例子中,我是根據(jù)反編譯后的匯編碼得到的參數(shù)間隔,還好都是4,然后在代碼中寫死了。為了滿足代碼的可移植性,C標(biāo)準(zhǔn)庫在stdarg.h中提供了諸多Facilities以供實(shí)現(xiàn)變長長度參數(shù)時(shí)使用。這里也列出一個(gè)簡單的例子,看看利用標(biāo)準(zhǔn)庫是如何支持變長參數(shù)的:#include vo
10、id std_vararg_func(const char *fmt, . ) va_list ap; va_start(ap, fmt); printf(%dn, va_arg(ap, int); printf(%fn, va_arg(ap, double); printf(%sn, va_arg(ap, char*); va_end(ap);int main() std_vararg_func(%d %f %sn, 4, 5.4, hello world);輸出:45.hello world對(duì)比一下 std_vararg_func和var_args_func的實(shí)現(xiàn),va_list似乎就是c
11、har*, va_start似乎就是 (char*)&fmt) + sizeof(fmt),va_arg似乎就是得到下一個(gè)參數(shù)的首地址。沒錯(cuò),多數(shù)平臺(tái)下stdarg.h中va_list, va_start和var_arg的實(shí)現(xiàn)就是類似這樣的。一般stdarg.h會(huì)包含很多宏,看起來比較復(fù)雜。在有的系統(tǒng)中stdarg.h的實(shí)現(xiàn)依賴some special functions built into the the compilation system to handle variable argument lists and stack allocations,多數(shù)其他系統(tǒng)的實(shí)現(xiàn)與下面很相似:(V
12、isual C+ 6.0的實(shí)現(xiàn)較為清晰,因?yàn)閣indows上的應(yīng)用程序只需要在windows平臺(tái)間做移植即可,沒有必要考慮太多的平臺(tái)情況)。C語言va_list與_vsnprintf的使用先舉一個(gè)例子:#define bufsize 80char bufferbufsize;/* 這個(gè)函數(shù)用來格式化帶參數(shù)的字符串*/int vspf(char *fmt, .) va_list argptr; /聲明一個(gè)轉(zhuǎn)換參數(shù)的變量 int cnt; va_start(argptr, fmt); /初始化變量 cnt = vsnprintf(buffer,bufsize ,fmt, argptr);/將帶參數(shù)
13、的字符串按照參數(shù)列表格式化到buffer中 va_end(argptr); /結(jié)束變量列表,和va_start成對(duì)使用 return(cnt); int main(int argc, char* argv) int inumber = 30; float fnumber = 90.0; char string4 = abc; vspf(%d %f %s, inumber, fnumber, string); printf(%sn, buffer); return 0;下面我們來探討如何寫一個(gè)簡單的可變參數(shù)的C函數(shù).寫可變參數(shù)的C函數(shù)要在程序中用到以下這些宏: 使用可變參數(shù)應(yīng)該有以下步驟: 1)
14、首先在函數(shù)里定義一個(gè)va_list型的變量,這里是arg_ptr,這個(gè)變量是指向參數(shù)的指針. 2)然后用va_start宏初始化變量arg_ptr,這個(gè)宏的第二個(gè)參數(shù)是第一個(gè)可變參數(shù)的前一個(gè)參數(shù),是一個(gè)固定的參數(shù). 3)然后用va_arg返回可變的參數(shù),并賦值給整數(shù)j. va_arg的第二個(gè)參數(shù)是你要返回的參數(shù)的類型,這里是int型. 4)最后用va_end宏結(jié)束可變參數(shù)的獲取.然后你就可以在函數(shù)里使用第二個(gè)參數(shù)了.如果函數(shù)有多個(gè)可變參數(shù)的,依次調(diào)用va_arg獲取各個(gè)參數(shù). 如果我們用下面三種方法調(diào)用的話,都是合法的,但結(jié)果卻不一樣: 可變參數(shù)在編譯器中的處理我們知道va_start,va_
15、arg,va_end是在stdarg.h中被定義成宏的,由于:1)硬件平臺(tái)的不同 2)編譯器的不同Microsoft Visual StudioVC98Includestdarg.h中,typedef char * va_list; /*把va_list被定義成char*,這是因?yàn)樵谖覀兡壳八玫腜C機(jī)上,字符指針類型可以用來存儲(chǔ)內(nèi)存單元地址。而在有的機(jī)器上va_list是被定義成void*的*/#define _INTSIZEOF(n) ( (sizeof(n) + sizeof(int) - 1) & (sizeof(int) - 1) )/*_INTSIZEOF (n)宏是為了考慮那些內(nèi)
16、存地址需要對(duì)齊的系統(tǒng),從宏的名字來應(yīng)該是跟sizeof(int)對(duì)齊。一般的sizeof(int)=4,也就是參數(shù)在內(nèi)存中的地址都為4的倍數(shù)。比如,如果sizeof(n)在14之間,那么_INTSIZEOF(n)4;如果sizeof(n)在58之間,那么 _INTSIZEOF(n)=8。*/#define va_start(ap,v) ( ap = (va_list)&v + _INTSIZEOF(v) )/*va_start 的定義為 &v+_INTSIZEOF(v) ,這里&v是最后一個(gè)固定參數(shù)的起始地址,再加上其實(shí)際占用大小后,就得到了第一個(gè)可變參數(shù)的起始內(nèi)存地址。所以我們運(yùn)行va_st
17、art (ap, v)以后,ap指向第一個(gè)可變參數(shù)在的內(nèi)存地址*/#define va_arg(ap,t) ( *(t *)(ap += _INTSIZEOF(t) - _INTSIZEOF(t) )/*這個(gè)宏做了兩個(gè)事情,用用戶輸入的類型名對(duì)參數(shù)地址進(jìn)行強(qiáng)制類型轉(zhuǎn)換,得到用戶所需要的值計(jì)算出本參數(shù)的實(shí)際大小,將指針調(diào)到本參數(shù)的結(jié)尾,也就是下一個(gè)參數(shù)的首地址,以便后續(xù)處理。*/#define va_end(ap) ( ap = (va_list)0 ) /*x86 平臺(tái)定義為ap=(char*)0;使ap不再指向堆棧,而是跟NULL一樣.有些直接定義為(void*)0),這樣編譯器不會(huì)為va_
18、end產(chǎn)生代碼,例如gcc在linux的x86平臺(tái)就是這樣定義的. 在這里大家要注意一個(gè)問題:由于參數(shù)的地址用于va_start宏,所以參數(shù)不能聲明為寄存器變量或作為函數(shù)或數(shù)組類型. */這里有兩個(gè)地方需要深入挖掘一下:1、#define _INTSIZEOF(n) ( (sizeof(n) + sizeof(int) - 1) & (sizeof(int) - 1) )我們這里簡化一下這個(gè)宏:#define _INTSIZEOF(n) (sizeof(n) + x) & (x)x = sizeof(int) - 1 = 3 = 0000 0000 0000 0011(b)x = 1111 1
19、111 1111 1100(b)當(dāng)一個(gè)數(shù) & (-x)時(shí),得到的值始終是sizeof(int)的倍數(shù),也就是說_INTSIZEOF(n)的功能是將n圓整到sizeof(int)的倍數(shù)上去。sizeof(n) = 1, sizeof(n)+sizeof(int)-1經(jīng)過圓整后,一定會(huì)是=4的整數(shù);在其他系統(tǒng)平臺(tái)上,圓整的目標(biāo)值有的是4,有的則是8,視具體系統(tǒng)而定。2、#define va_arg(ap,t) ( *(t *)(ap += _INTSIZEOF(t) - _INTSIZEOF(t) )其實(shí)有了var_args_func的實(shí)現(xiàn),這里也就不難理解了。不過這里有一個(gè)trick,很多人一開
20、始肯定對(duì)先加上_INTSIZEOF(t),又減去_INTSIZEOF(t)很不理解,其實(shí)這里是一點(diǎn)就透的:整個(gè)表達(dá)式(ap += _INTSIZEOF(t) - _INTSIZEOF(t) 返回的值其實(shí)和最初的ap所指向的地址是一致的,關(guān)鍵就是在整個(gè)表達(dá)式被evaluated后,ap卻指向了下一個(gè)參數(shù)的地址了,就這么簡單。C語言的函數(shù)是從右向左壓入堆棧的,圖(1)是函數(shù)的參數(shù)在堆棧中的分布位置.我們看到va_list被定義成char*,有一些平臺(tái)或操作系統(tǒng)定義為void*.再看va_start的定義,定義為&v+_INTSIZEOF(v),而&v是固定參數(shù)在堆棧的地址,所以我們運(yùn)行va_sta
21、rt(ap, v)以后,ap指向第一個(gè)可變參數(shù)在堆棧的地址,如圖:高地址|-| |函數(shù)返回地址 | |-| |. | |-| |第n個(gè)參數(shù)(第一個(gè)可變參數(shù)) | |-|-va_start后ap指向 |第n-1個(gè)參數(shù)(最后一個(gè)固定參數(shù))| 低地址|-|- &v 圖( 1 )然后,我們用va_arg()取得類型t的可變參數(shù)值,以上例為int型為例,我們看一下va_arg取int型的返回值: j= ( *(int*)(ap += _INTSIZEOF(int)-_INTSIZEOF(int) ); 首先ap+=sizeof(int),已經(jīng)指向下一個(gè)參數(shù)的地址了.然后返回ap-sizeof(int)的
22、int*指針,這正是第一個(gè)可變參數(shù)在堆棧里的地址(圖2).然后用*取得這個(gè)地址的內(nèi)容(參數(shù)值)賦給j.高地址|-| |函數(shù)返回地址 | |-| |. | |-|-va_arg后ap指向 |第n個(gè)參數(shù)(第一個(gè)可變參數(shù)) | |-|-va_start后ap指向 |第n-1個(gè)參數(shù)(最后一個(gè)固定參數(shù))| 低地址|-|- &v 圖( 2 )最后要說的是va_end宏的意思,x86平臺(tái)定義為ap=(char*)0;使ap不再指向堆棧,而是跟NULL一樣.有些直接定義為(void*)0),這樣編譯器不會(huì)為va_end產(chǎn)生代碼,例如gcc在linux的x86平臺(tái)就是這樣定義的.在這里大家要注意一個(gè)問題:由于參
23、數(shù)的地址用于va_start宏,所以參數(shù)不能聲明為寄存器變量或作為函數(shù)或數(shù)組類型. 關(guān)于va_start, va_arg, va_end的描述就是這些了,我們要注意的是不同的操作系統(tǒng)和硬件平臺(tái)的定義有些不同,但原理卻是相似的.可變參數(shù)在編程中要注意的問題因?yàn)関a_start, va_arg, va_end等定義成宏,所以它顯得很愚蠢,可變參數(shù)的類型和個(gè)數(shù)完全在該函數(shù)中由程序代碼控制,它并不能智能地識(shí)別不同參數(shù)的個(gè)數(shù)和類型. 有人會(huì)問:那么printf中不是實(shí)現(xiàn)了智能識(shí)別參數(shù)嗎?那是因?yàn)楹瘮?shù) printf是從固定參數(shù)format字符串來分析出參數(shù)的類型,再調(diào)用va_arg的來獲取可變參數(shù)的.也就
24、是說,你想實(shí)現(xiàn)智能識(shí)別可變參數(shù)的話是要通過在自己的程序里作判斷來實(shí)現(xiàn)的. 另外有一個(gè)問題,因?yàn)榫幾g器對(duì)可變參數(shù)的函數(shù)的原型檢查不夠嚴(yán)格,對(duì)編程查錯(cuò)不利.如果simple_va_fun()改為: void simple_va_fun(int i, .) va_list arg_ptr; char *s=NULL;va_start(arg_ptr, i); s=va_arg(arg_ptr, char*); va_end(arg_ptr); printf(%d %sn, i, s); return 0; 可變參數(shù)為char*型,當(dāng)我們忘記用兩個(gè)參數(shù)來調(diào)用該函數(shù)時(shí),就會(huì)出現(xiàn)core dump(Uni
25、x) 或者頁面非法的錯(cuò)誤(window平臺(tái)).但也有可能不出錯(cuò),但錯(cuò)誤卻是難以發(fā)現(xiàn),不利于我們寫出高質(zhì)量的程序. 以下提一下va系列宏的兼容性. System V Unix把va_start定義為只有一個(gè)參數(shù)的宏: va_start(va_list arg_ptr); 而ANSI C則定義為: va_start(va_list arg_ptr, prev_param); 如果我們要用system V的定義,應(yīng)該用vararg.h頭文件中所定義的宏,ANSI C的宏跟system V的宏是不兼容的,我們一般都用ANSI C,所以用ANSI C的定義就夠了,也便于程序的移植.小結(jié): 可變參數(shù)的函數(shù)原理其實(shí)很簡單,而va系列是以宏定義來定義的,實(shí)現(xiàn)跟堆棧相關(guān).我們寫一個(gè)可變函數(shù)的C函數(shù)時(shí),有利也有弊,
溫馨提示
- 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請(qǐng)下載最新的WinRAR軟件解壓。
- 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請(qǐng)聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
- 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會(huì)有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
- 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
- 5. 人人文庫網(wǎng)僅提供信息存儲(chǔ)空間,僅對(duì)用戶上傳內(nèi)容的表現(xiàn)方式做保護(hù)處理,對(duì)用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對(duì)任何下載內(nèi)容負(fù)責(zé)。
- 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請(qǐng)與我們聯(lián)系,我們立即糾正。
- 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時(shí)也不承擔(dān)用戶因使用這些下載資源對(duì)自己和他人造成任何形式的傷害或損失。
最新文檔
- 人力資源招聘居間合同格式
- 文創(chuàng)園區(qū)衛(wèi)生間翻新合同
- 牛棚承包合同
- 化工產(chǎn)品購銷合同
- 電商承包合同協(xié)議書
- 玩具銷售合同范例
- 喝啤酒大賽比賽規(guī)則
- 場(chǎng)地租賃合同協(xié)議書
- 統(tǒng)編版初中語文七年級(jí)上冊(cè)第九課《從百草園到三味書屋》聽評(píng)課記錄
- 企業(yè)戰(zhàn)略規(guī)劃知識(shí)管理系統(tǒng)作業(yè)指導(dǎo)書
- 最新卷宗的整理、裝訂(全)課件
- 城市旅行珠海景色介紹珠海旅游攻略PPT圖文課件
- 信訪事項(xiàng)受理、辦理、復(fù)查、復(fù)核、聽證程序課件
- 【北京】施工現(xiàn)場(chǎng)安全生產(chǎn)標(biāo)準(zhǔn)化管理圖集
- 部編版小學(xué)道德與法治五年級(jí)下冊(cè)教案(全冊(cè))
- 小學(xué) 三年級(jí) 科學(xué)《觀測(cè)風(fēng)》教學(xué)設(shè)計(jì)
- JJF1664-2017溫度顯示儀校準(zhǔn)規(guī)范-(高清現(xiàn)行)
- 第二講共振理論、有機(jī)酸堿理論
- 研究性學(xué)習(xí)課題——有趣對(duì)聯(lián)
- 高考英語聽力必備場(chǎng)景詞匯精選(必看)
- 電鍍工業(yè)園項(xiàng)目可行性研究報(bào)告-用于立項(xiàng)備案
評(píng)論
0/150
提交評(píng)論