国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 學院 > 開發設計 > 正文

GNU 編譯器家族 GCC 內部探密

2019-11-17 05:14:25
字體:
來源:轉載
供稿:網友

探索 GCC 前端的內部結構

趙蔚 (zhaoway@public1.ptt.js.cn)
linux 和自由軟件技術獨立顧問
2003 年 7 月

我們在本文中說明 GCC 源碼包中的例子編程語言 Treelang 的實現細節。主要目的在于輯此說明所謂 GCC 前端的編程方法。限于篇幅,本文只能略略講一下 GCC 前端的內部結構的框架部分。本文中所涉及到的源程序均位于 GCC 源碼包中的 gcc/ 目錄和 gcc/treelang/ 目錄下。本文的代碼分析基于 GCC CVS 中的最新(2003 年六月)的開發版本。

GNU 編譯器家族 GCC 介紹
作為自由軟件的旗艦項目,Richard Stallman 在十多年前剛開始寫作 GCC 的時候,還只是把它當作僅僅一個 C 程序語言的編譯器;GCC 的意思也只是 GNU C Compiler 而已。經過了這么多年的發展,GCC 已經不僅僅能支持 C 語言;它現在還支持 Ada 語言,C++ 語言,java 語言,Objective C 語言,Pascal 語言,COBOL 語言,以及支持函數式編程和邏輯編程的 Mercury 語言,等等。而 GCC 也不再單只是 GNU C 語言編譯器的意思了,而是變成了 GNU Compiler Collection 也即是 GNU 編譯器家族的意思了。

另一方面,說到 GCC 對于各種硬件平臺的支持,概括起來就是一句話:無所不在。幾乎所有有點實際用途的硬件平臺,甚至包括有些不那么有實際用途的硬件平臺,比如 Don Knuth 設計的 MMIX 計算機,GCC 都提供了完善的支持。

我們在這篇文章中要弄清楚的就是 GCC 是如何做到能夠支持這么多種程序語言的。所謂的 GCC 的程序語言前端到底是怎么回事。假如我們要設計實現自己的編程語言的話,應該從何入手。回答這些問題的第一步,就是分析清楚 GCC 源碼包中,為了說明 GCC 的程序語言前端的編寫方法,而寫作的 Treelang 編程語言在 GCC 中的實現細節。

假如把我們自己的程序語言的實現建立于 GCC 之上,也馬上使得我們的程序語言的實現版本可以運行在幾乎所有有用的硬件平臺之上。這對于程序語言的作者來說,也是一個確實的有極大誘惑力的好處。

關于代碼分析
在這一小節里面我們著重說明兩個問題:第一、為什么要閱讀源代碼;第二、代碼分析應該怎么寫。

閱讀源代碼對提高自己的編程水平是非常有幫助的。這個幫助至少體現在兩個方面。第一個方面是學會大型軟件項目設計的模式。這樣的模式是真實可靠的第一手資料,這樣學來的模式要比從書本上,用日常語言陳述的模式,更能深入到你的腦海中去。而且它的真實性和可靠性都是有保證的。并且這樣的模式還非常的具體。我曾經看到計算機系的同學推薦去讀亞歷山大的建筑學方面的經典著作;個人以為這是走的太遠了。與其去讀建筑學的書,不如去分析一下成功的自由軟件項目的源代碼。具體的用代碼說明的模式,無論如何要比虛無飄渺的美學概念,或者模棱兩可的工程紀律,都要更加輕易學習吧?

閱讀源代碼的第二個好處,是增加自己的自信心。就象學習英語,要和別人談話,要看看別人的文章,不能只是看教科書上的簡單的例子。教科書上的例子限于篇幅,不可能做到像真實、完整的英文小說那樣,把一個完整的設計呈現在你的面前。只有當你硬著頭皮,拋開字典,把一本英文小說生生啃下來之后,你才能有把握說:我的確能做到。類似的,只有當我們看過大型軟件項目的源代碼,作過修改,摸爬滾打之后,我們才能有把握的說:我也能寫出來。

上面說了閱讀源代碼至少有這么兩個好處。那么在閱讀源代碼的時候,我們必然要做代碼分析筆記。這個代碼分析筆記如何寫,這就是我們關心的一個問題了。在這里,我提出一些我自己的也許不太成熟的看法,也請讀者朋友們不吝指教。

我總覺得,與其作一行一行的代碼注釋,說明每一行代碼的作用;不如設計一個故事,把代碼的框架說清楚。這也是我前面提到的,所謂模式一說。因為閱讀源代碼,最要害的是要了解大型軟件項目設計的模式,而不是要把每一次讀者分析每一行代碼細節的樂趣從此剝奪掉。

另一方面,代碼分析的寫作風格,可以是參考手冊似的;也可以是航海日志似的。我個人覺得參考手冊似的代碼分析是比較乏味的,讀起來乏味,寫起來也不免乏味,雖然它可能更有用。對于一個急著要快點結束加班工作的軟件工程師來說,也許參考手冊更加實用。但是對于一個想要了解這一份成功的軟件背后的工作奧秘的探索者來說,一個航海日志似的代碼分析,也許讀起來更有味道,更能讓一個程序員在鍵盤與屏幕之間,體會到那地理大發現的激動與樂趣。

本文后面的代碼分析,就是希望能寫成這樣的風格。可是作者筆力有限,如有不足之處,還請讀者朋友們不吝指教。

Treelang 的代碼框架
讀者朋友們在閱讀這一部分代碼分析的時候,手邊最好能預備上一份 GCC 3.3 的源代碼。這個源代碼可以從 GCC 的站點 http://gcc.gnu.org 上獲得。本文作者力圖做到把整個情況像說故事一樣娓娓道來,但是讀者朋友們假如在適當的時候能夠查閱一下源代碼,可能更能把問題了解的清楚透徹。

這個 treelang 語言的實現,主要有兩個 C 語言文件,把整個代碼框架分成兩個部分。第一部分以 tree1.c 為主,帶上 parse.y 這個 YACC 源程序,組成了和 GCC 前端的接口;第二部分以 treetree.c 為主,組成了和 GCC 后端的接口。


這里首先說明一下 tree1.c 這個文件。它和上級目錄中的 GCC 框架文件 toplev.c 交互作用,實現 tree1 這個執行程序的主體部分。這個 tree1 就相當于 GCC 的 C 語言前端中的 cc1 執行程序,該程序是 C 語言編譯器前端的主體。

我們首先試圖說明從 toplev.c 到 tree1.c 的路徑。這樣我們就注重到 toplev.c 中這個引人注目的 lang_hooks 變量。當然,接下來就注重到在 toplev.c 同一目錄下的 langhooks.c 這個文件。我們希望在其中發現一點有趣的東西。這一共是三個文件:langhooks.[ch] 和 langhooks-def.h 其中在 langhooks.h 中定義了一堆各式各樣的 strUCt lang_hooks_for_xxx 結構,以及最后還有一個 struct lang_hooks 結構把前面的那些 for_xxx 的結構都總括了起來。這每一個結構都是若干個至少看上去像是回調函數的函數指針。看來這就是我們要尋找的東西。那么大概就是這樣了,編譯器前端向 GCC 主體部分注冊自己的 lang_hooks 來完成各樣的任務。接下來一個自然的問題就是這個注冊是如何進行的;另外一個問題就是要對這些回調函數指針進行分析了。

這個 langhooks.h 文件中關于 struct lang_hooks 結構字段的注釋很具體,這里我們暫時先跳過去。等到 treelang 中具體的注冊回調函數出現的時候,我們根據需要再做仔細說明。在 langhooks-def.h 文件中定義了一些這個 struct lang_hooks 結構的默認值。

現在我們進入 treelang 目錄下的 treetree.c 這個文件。來察看一下在 treelang 中對 struct lang_hooks 這個結構的初始化過程。這個過程不是按照我們通常所熟悉的 C 語言的 C99 標準或者是 GCC 擴展語法來進行的。而是采用了大量的 #define 和 #undef 并結合上層目錄中的 langhooks-def.h 來進行。細想一下,這是理所當然的事情,因為這是在編譯 C 語言編譯器本身嘛。當然就不好用到 C 語言的新的東西或者是自己做的擴展的東西。

注釋開始:::::

我們以初始化如下定義的 struct sample 結構為例。


struct sample {
int member_int;
char *member_str;
void (*member_fun)(void);
};

在 C99 中,初始化一個 struct 結構數據,使用下面這樣的語法。


struct sample inst_c99 = {
.member_int = 78,
.member_str = "iloveqhq",
.member_fun = real_fun,
};

在 C99 標準出現之前,GCC 定義了自己的擴展,下面的例子就是按照這個 GCC 對 C 語言的擴展,來初始化一個 struct 結構數據。


struct sample inst_gcc = {
member_int: 76,
member_str: "zhaoway",
member_fun: real_fun,
};

在 GCC 的源代碼中沒有使用上面的兩種辦法,而是大量使用了宏定義。這個辦法首先要申明一份輔助的宏定義。這些個輔助的宏定義,在一個軟件項目里面,針對一個 struct 結構,只需要一份即可。


#define MEMBER_INT 0
#define MEMBER_STR ""
#define MEMBER_FUN NULL

#define SAMPLE_INITIALIZER {
MEMBER_INT,
MEMBER_STR,
MEMBER_FUN,
}

按照上面這樣的辦法申明了這些關于這個 struct sample 的輔助宏定義以后,在每次要初始化一個 struct sample 數據結構的時候,只需要按照如下操作即可。除了要稍微多打一些字以外,這個方法的方便程度和以上兩種方法是差不多的。


#undef MEMBER_INT
#define MEMBER_INT 12
#undef MEMBER_STR
#define MEMBER_STR "trtr"
#undef MEMBER_FUN
#define MEMBER_FUN real_fun
struct sample inst_def = SAMPLE_INITIALIZER;

這樣就也可以像 C99 標準或者 GCC 的擴展一樣,按照成員變量的名稱來初始化一個 struct 類型的數據結構了。不過話又說回來,在我們一般的軟件項目中,還是應該沿著 C99 標準這個 C 語言的發展方向來走的。

:::::注釋結束

接下來的線路很清楚,就是一個一個的分析這些個回調函數啦。

對用戶源文件進行語法分析
這個 treelang 注冊的這些回調函數在 GCC 主框架那里被調用的順序,我們暫時還不想深入。揀有意思的先看看吧。首先關注的是 treelang_parse_file 這個函數。在 langhooks.h 里面關于這個回調函數所作的注釋說明,是要它對用戶的整個源文件進行語法分析。因為這個函數的返回值是 void 所以我們預期它是通過設置某一個全局變量來完成任務的;但是也有另外一種可能,就是它會把所有要做的事情都給做完,這樣它也就自然不需要返回值了。這兩種可能我們現在還不能確定。讓我們往下看吧。


這個 treelang_parse_file 函數在 tree1.c 中定義,這是屬于到 GCC 前端的接口。它直接就跑去調用 yyparse 這個 YACC 主函數了。這倒是簡單,呵呵。可是要我們從 parse.y 文件中理出個頭緒來,這個文件有超過 900 行的 YACC 代碼,未免有點麻煩。最要害的是這中間數據的交流不大輕易看清楚,不像回調函數指針這樣顯而易見。假如程序果真是通過設置一些全局變量來完成任務的話,我們的分析任務就有點棘手了。

注釋開始:::::

在這里先說一下 tree 這個數據結構。這是 GCC 圍繞著 C 和 C++ 語言的語法分析,用到的主要數據結構。所有其它語言的編譯器前端,也都需要在語法分析階段結束以后,為 GCC 生成相應的 tree 結構的數據。然后 GCC 的后端就可以從 tree 生成獨立于平臺的 RTL 數據結構,并隨后生成相應平臺上的機器語言代碼。所以作為 GCC 的編譯器前端,這里的主要工作就是從一個文本文件,也就是源代碼,生成這個 tree 結構的數據,喂給編譯器的后端。我們看到,前端是依靠于編程語言的;后端是依靠于機器平臺的;中間的 tree 和 RTL 則獨立于編程語言和機器平臺。但是話雖如此說,這個 tree 和 RTL 數據結構也還是主要以 C 和 C++ 語言為考慮問題的中心。這是不可避免的事情。

:::::注釋結束

好啦,沒辦法啦。我們這就開始從 treelang 目錄下的 parse.y 一行一行的往下瞅吧。這個 Treelang 程序語言的語法很簡單,我們看到哪兒,說到哪兒。

注釋開始:::::

在看 GCC 的源代碼的時候經常會碰到 GTY 這個東西。這是 GCC 內部的內存治理機制所需要的,在 C 語言代碼上添加的一些類型信息,這些類型信息在 GCC 內部做垃圾收集的時候會用到。這個細節我們這里先忽略過去,以后講到相關內容的時候再做說明。

:::::注釋結束

在 parse.y 中的一些主要的產生式上所匹配的 C 函數,它們所做的工作大體上都是首先根據語法分析的結果,把自己定義的結構 struct PRod_token_parm_item 里面的數據先給設置好;然后根據情況調用在 treetree.c 中定義的相關函數,生成 tree 結構的數據;這之后再把返回來的 tree 結構數據記錄在 struct prod_token_parm_item 里面,并把整個結構的數據放到 symbol_table 這個單向鏈表上。這樣看來,似乎這個 symbol_table 就是我們前面所要尋找的全局變量了。是不是在語法分析任務完成以后,就獲得了這個全局變量;然后依靠于這個全局變量,后續任務才得以獲得輸入數據,繼續往下執行呢?

我們來仔細看一看 tree1.c 中這個 symbol_table 變量的定義如下。


static GTY(()) struct prod_token_parm_item *symbol_table = NULL;

注重到這是被申明為 static 的變量。在 Samuel P. Harbison III 和 Guy L. Steele, Jr 所合著的 C: A Reference Manual 的英文版的第五版第八十三頁上,關于 static 變量有如下說明:"On data declarations, it always signifies a defining declaration that is not eXPorted to the linker."換句話說,這個 static 的 symbol_table 變量,在 tree1.o 之外是看不見的。這不可能是我們所要尋找的全局變量。

可是,另一方面,除了這個變量有點像是那么一回事之外,其它的就再也沒有什么有趣的變量了。這是怎么一回事呢?我們先不管它,往下看了再說吧。

那么這個 parse.y 文件大體如是啦。其它的一些具體的細節問題,牽涉到 Treelang 程序語言的具體定義,暫且不是我們的愛好所在。粗粗的看一遍下來,這個語法分析的過程,從 GCC 的主體結構上,經由 lang_hooks 進入 treelang 部分的 yyparse 函數,這個函數按照語法定義,把編譯器用戶輸入的 Treelang 語言的源程序分解成若干類型的小塊,加以分析,生成自己定義的 struct prod_token_parm_item 結構的數據,再把這些數據一個一個串到 symbol_table 這個鏈表上面;這樣就算完成任務了。線索從 lang_hooks 中定義的這個回調函數撤出,再度回到 GCC 的主體框架。

對了,上面還忘了說,在把用戶輸入的 Treelang 語言的源程序進行分解以后,在分析的過程中,按照各種類型的小塊,還生成了相應的 tree 結構的數據,一起記錄在各自的 struct prod_token_parm_item 結構里面,這樣就一并把這個 tree 結構的數據也都放在了 symbol_table 這個鏈表里了。

接下往返到 GCC 的主體框架上的 toplev.c 文件。可是迷惑人的事情出現了,在函數 compile_file 對回調函數 treelang_parse_file 進行調用之后,無論是在 toplev.c 文件中,還是說在哪一個其它的回調函數里也好,似乎都并沒有什么有趣的事情發生了。這讓我們如何是好?看來我們只有回過頭去仔細跟蹤 treelang 目錄下的 treetree.c 文件中的那些函數,看看它們在被 parse.y 中的產生式調用執行的時候,到底干了些什么。

語法分析的細節
根據從 parse.y 這個 YACC 文件中的產生式得來的線索,我們首先關注 treetree.c 文件中的 tree_code_create_variable 這個函數。從那個 YACC 產生式,我們估計這個函數是為一個變量申明而構造必要的 tree 數據結構。這個函數有 100 行不到的源代碼。我們來仔細的看一看。這個函數使用了從 GCC 的框架結構里面來的關于 tree 數據結構的一些 API 接口。我們目前所最感愛好的,就是這個函數在利用這些接口函數構造一個和所對應的 YACC 產生式相當的 tree 結構數據以外,還干了些什么。我們之所以關心這個"以外",是因為目前我們最想了解的,是這個從 Treelang 語言的源程序開始,到一連串的 tree 結構數據,然后是怎么變成 RTL 結構的數據的。只有在有了這樣一個概觀以后,我們對 GCC 前端的編寫方法才能算有了一個初步的大概的了解。


根據這樣的思路,我們很快就看清楚,在這個 tree_code_create_variable 函數中,在設置好若干個局部的 tree 結構的數據以后,引人注目的在一個 if 語句的分支中調用了 rest_of_decl_compilation 這個函數。而且在這個函數被調用返回以后,似乎不再有重要的事情發生了。這個函數來自于 GCC 框架結構上的 toplev.c 文件。這樣的話,根據我們前面的分析,這個函數里面應該會隱藏有我們的主要問題的答案。也就是說,在 YACC 文件 parse.y 把用戶提供的 Treelang 語言的源文件肢解以后,在 treetree.c 中的相應的函數,為之生成了相應的 tree 結構數據,而在現在我們所關注的這個 rest_of_decl_compilation 函數(以及在這個 if 語句的另一個分支中出現的一系列相應的函數)中,應該會完成從 tree 結構的數據到 RTL 數據的翻譯

從另一個角度補充一點,程序的執行線索是如何從 GCC 主框架進入 parse.y 中的呢?這一段我們前面分析過了,現在再來提醒一下。這是從 GCC 的框架結構,進入到 treelang 這個 GCC 的語言前端模塊注冊的 lang_hooks 結構的數據,找到相應的回調函數,最終找到 parse.y 這個 YACC 程序的入口 yyparse 函數的。在 yyparse 之后,我們看到程序的主線索進入了 treelang 目錄下的 treetree.c 文件中的函數。最后,我們重新又追蹤到 GCC 主體部分的 toplev.c 文件中的函數。現在我們的整個圖景的大輪廓就快要完全弄清楚了。

GCC 前端的全景圖
終于,我們在 rest_of_decl_compilation 函數中,看到了一系列的和 RTL 相關的函數調用。稍微仔細的看了一遍之后,我們有把握得出這個結論了。我們在本文的開頭部分,曾經猜想 GCC 的主體部分在要求 GCC 這個 Treelang 語言前端從用戶提供的 Treelang 語言的源程序文本,經過語法分析,得出相應的 tree 結構數據以后,會把這個數據通過函數返回值傳回給 GCC 的主體程序,或者設置一個全局變量,這樣就算完成任務了。但是事實上,經過我們上面的分析,發現不是這么一回事。

相反的,在 Treelang 這個語言前端得到需要的 tree 結構的數據以后,繼續往下的運行,這完全是 Treelang 前端必須自己負責的任務。這個 GCC 前端必須自己調用 GCC 主體部分提供的,用來從 tree 結構數據生成 RTL 結構數據的函數接口,以完成從 tree 結構數據到 RTL 結構數據的翻譯過程。這樣,這個 GCC 的語言前端的任務才算完成。換句話說,GCC 的這個語言前端承擔的角色是非常的主動的。很明顯,這樣的設計提供給我們極大的靈活性。關于這一點,我們以后會逐漸看到。

小結
本文限于篇幅,只大略講述了 GCC 前端的框架結構,給出了一個粗略的全景圖。在以后的幾篇文章中,我們將進一步探索 GCC 的主體部分為 GCC 前端所提供的 API 函數和數據結構。并利用這些知識,探索一下為 GCC 編寫一個 Scheme 語言前端的可能性。在這一系列文章結束的時候,希望能使得讀者朋友們對 GCC 以及程序語言的本質有一個更加深刻的了解。也希望 GCC 的前端的作者人數,就能和 Linux 內核模塊的作者人數一樣多。我們的座右銘是:每一個人都是程序員;每一個人都能加載自己編寫的內核模塊;每一個人都能使用自己實現的編程語言!(不要害怕,這只是一句玩笑話。呵呵。)

在技術內容以外,本文也探索了開放源碼運動所需要的技術文檔的一種寫作模式。開放源碼運動為我們帶來了大量的自由軟件的源程序。對于用戶來說,需要文檔講述如何使用這些自由軟件;對于程序員來說,則需要文檔講述如何才能理解并真正的把握這些自由軟件的源程序。這第二種文檔的寫作,不是一件輕易的事情。作者本人在經常閱讀解釋自由軟件的源程序的內部運作機理的文檔的過程中,總是覺得這件事情應該可以有辦法做的更好。本文就是作者的一個嘗試。希望讀者朋友們給我來信,不僅僅討論 GCC 的技術問題,也歡迎對作者的寫作方式提出批評與指教!

參考資料

* Internals of the GNU Compiler Collection http://gcc.gnu.org/onlinedocs/gccint/ 這其實也就是 info gccint 的內容。這份文檔是除了源代碼以外最權威的資料了。不過它的可讀性恐怕不是那么好。初上手閱讀的時候,恐怕會非常困惑的。

* The GNU Treelang Compiler 手冊,這其實也就是 info treelang 的內容。這個作為例子的 Treelang 程序語言基于 Tim Josling(見下)為了闡明 GCC 前端的編寫方法而發明的一個玩具編程語言。

* Sreejith K Menon 編寫的 GCC Frontend HOWTO http://www.tldp.org/HOWTO/GCC-Frontend-HOWTO.Html 被收錄在 The Linux Documentation Project 的 HOWTO 文集中。這份資料可能是可讀性最強的了吧?當然,這是說除了本文之外啦。:-)

* Using, Maintaining and Enhancing COBOL for the GNU Compiler Collection http://cobolforgcc.sourceforge.net/cobol_toc.html 這是由 Tim Josling 領導的 COBOL for GCC 項目的文檔,其中原先由 Joachim Nadler 所編寫的第十四章,后來由 Tim Josling 從德文翻譯成英文;這一章講述了 GCC 前端的編寫方法。

* Using and Porting GNU Fortran 手冊 http://gcc.gnu.org/onlinedocs/g77/ 中關于 Front End 的一章也講述了我們感愛好的內容。

* 自由大百科全書 Wikipedia 中關于 GCC 編譯器家族的條目 http://www.wikipedia.org/wiki/GNU_Compiler_Collection 對 GCC 有個概括介紹。關于 GCC 內的 RTL 數據結構的條目 http://www.wikipedia.org/wiki/Register_Transfer_Language 以及關于 GCC 的前端使用的 Tree 數據結構的條目 http://www.wikipedia.org/wiki/GCC_Abstract_Syntax_Tree 也都值得一看。


* 用 Doxygen 文檔生成工具制作的 GCC Source Documentation http://www.nondot.org/gcc/ 有愛好的話也可以看一看。

* 在閱讀大型的 C 語言項目的源代碼的時候,手頭有一本好的、全的 C 語言參考手冊也是很重要的。
關于 C 語言的一本比較好的書是 Samuel P. Harbison III 和 Guy L. Steele, Jr. 合著的 C: A Reference Manual 第五版。這本書的英文影印版最近在國內出版了。作者之一 Guy L. Steele, Jr. 是 Scheme 編程語言的發明人之一,也是 Java 語言規范的作者之一,更是 ACM 的 Grace Murray Hopper 獎 1988 年的獲得者。

關于作者
趙蔚,南京的 Linux 和自由軟件技術獨立顧問。他的網絡日記http://www.advogato.org/person/zhaoway的前言部分列有他在網絡上發表的技術文章的一份清單。他還有另外一處網絡日記http://www.kuro5hin.org/user/zhaoway/diary您可以在上面發表評論。他也經常以 iloveqhq 的網名登錄南京大學小百合 BBS 站 http://bbs.nju.edu.cn 的 LinuxUnix 版和 CompLang 版參加討論。您可能通過 zhaoway@public1.ptt.js.cn 與他聯系。
來自:www-900.ibm.com

上一篇:gdb和core

下一篇:qt簡介

發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
主站蜘蛛池模板: 高雄市| 德庆县| 厦门市| 全州县| 陆良县| 双城市| 民勤县| 怀远县| 昌平区| 榆树市| 抚松县| 翁牛特旗| 平邑县| 巴马| 伽师县| 天气| 武鸣县| 海阳市| 平潭县| 绥阳县| 南宁市| 灌云县| 越西县| 东山县| 永登县| 抚顺市| 方正县| 离岛区| 镇坪县| 陕西省| 安化县| 鸡东县| 吴桥县| 宾川县| 安康市| 贵南县| 景谷| 台东市| 秦皇岛市| 永康市| 邵武市|