unix環境高級編程 APUE.H最簡單編譯方法(第二版) Linux和Ubuntu

/*******************************************************************************
*第 0 種-最簡單實用
*
*******************************************************************************/.
1.直接進入源碼目錄的lib目錄
 cd lib
2.執行make命令
 make -f linux.mk
3.把生成的libapue.a與apue.h拷貝到你的源代碼目錄。如你的file目錄下
4.使用gcc -o ls1 ls1.c  libapue.a來編譯你的源代碼
5.成功

/*******************************************************************************
*第 1 種 方法大致同上
*
*******************************************************************************/.

《UNIX環境高級編程》(這裏使用的是第二版本的源碼)每個歷程中,都會有這樣一行源碼:
#include "apue.h"
    這個頭文件是作者把把每個例程中常用的標準頭文件,一些常用的出錯處理函數(err_**()之類的函

數)和一些常用的宏定義給整理在一個頭文件中。這個可以省去在每個例程中錄入較多的重複代碼,這樣可

以減少每個例程的長度。給讀者帶來了不少麻煩。下面給出一種源代碼的編譯方法。


1、解壓文件到apue.2e目錄
2、修改相應平臺的文件,我使用的是linux,所以修改Make.defines.linux
你修改的只需要這一行WKDIR=/home/your_dir/apue2e_src/apue.2e,改成自己的目錄路徑
3、cd到apue.2e目錄執行make -f linux.mk,之後你會在lib目錄下面找到libapue.a這個文件.
現在,你可以把它拷貝到你能尋找的地方,在編寫例子的時候,你就可以

4、拷貝apue2e_src/apue.2e/include/apue.h和apue2e_src/apue.2e/lib/libapue.a

到你的源代碼目錄。

5、使用gcc -o hello hello.c libapue.a來編譯你的源代碼

/*******************************************************************************
*第 2 種
*
*******************************************************************************/

最近在看apue的第二版,剛纔在Linux下把隨書的源代碼編譯了一遍,還是稍微花了點時間,作爲備忘把編譯過

程記錄下來
隨書的源代碼可從www.apuebook.com上獲得,下載後的解壓得到名爲apue.2e的目錄,在我的系統中該目錄的

完整路徑爲/home/se/apue.2e
接着首先是要閱讀/home/se/apue.2e/README,這是由apue第二版的作者Steve Rago寫的如何編譯隨書代碼的

基本指導以及部分自本書第一版以來的更改,主要內容如下:
Some source changes needed to be made after the book went out for the first
printing.  I forgot to make corresponding changes in the source tree on the
system used to develop the book.  The changes are summarized below.
1. lib/recvfd.c and sockets/recvfd.c - needed sys/uio.h on Mac OS X
2. lib/sendfd.c and sockets/sendfd.c - needed sys/uio.h on Mac OS X
3. stdio/buf.c - added code for Mac OS X
4. threadctl/suspend.c - changed wait to waitloc to avoid symbol definition
 clash on Solaris
5. include/apue.h - FreeBSD compiles work better if we rely on the default
 system settings.  Solaris needed a different XOPEN_SOURCE definition
 and also a CMSG_LEN definition.
To build the source, edit the Make.defines.* file for your system and set
WKDIR to the pathname of the tree containing the source code.  Then just
run "make".  It should figure out the system type and build the source for
that platform automatically.  If you are running on a system other than
FreeBSD, Linux, Mac OS X, or Solaris, you'll need to modify the makefiles
to include the settings for your system.  Also, you'll probably need to
modify the source code to get it to build on a different operating system.
The example source was compiled and tested using FreeBSD 5.2.1, Linux 2.4.22,
Mac OS X 10.3, and Solaris 9.
For FAQs, updated source code, and the lost chapter, see http://www.apuebook.com.
Please direct questions, suggestions, and bug reports to [email protected].
 
基本內容就是你用的系統如果是FreeBSD,Linux,Mac OS X或是Solaris,那麼你只要修改相應的

Make.defines.*文件(即如果你使用的是Linux,那麼你需要修改 Make.defines.linux文件的內容),將其中的

設置改爲你自己系統的設置然後在apue.2e目錄下運行make就ok了.所有的代碼都在 FreeBSD 5.2.1,Linux 

2.4.22,Mac OS X 10.3,Solaris 9上編譯通過.
 
總的來說要編譯成功是很簡單的,但總會因爲平臺的不同會出現一些錯誤,這時你就要根據自己系統的配置情

況來進行修改了
1.首先粗略的看了一下makefile的內容,make首先會執行腳本文件systype.sh,判斷所用系統的類型,然後根

據該類型選擇對應的Make.defines文件.這裏所要做的就是給systype.sh添加執行權限,chmod u+x 

systype.sh
2.因爲我用的是Linux,所以先看Make.defines.linux,需要修改的地方是WKDIR=/home/sar/apue.2e,把WKDIR

改爲你自己的工作目錄,在我這就是改爲WKDIR=/home/se/apue.2e,這個路徑在編譯時尋找"apue.h"頭文件時

使用.

3.然後我嘗試性的運行了一次make,果然有問題,在進入std目錄後報錯了,說找不到nawk命令,nawk是new 

awk,而我的系統上只有awk,這時你有兩種選擇,可以在運行make之前執行alias nawk='awk',這樣本質上是給

awk取了個叫nawk的別名,實際上運行的還是awk,另一種方法就是修改WKDIR/std /linux.mk,把第10行和15行

中的nawk都改爲awk,至於什麼是awk和nawk,以及它們的使用方法可以參考我之前收藏的一篇文章

http://www.360doc.com/showWeb/0/0/308938.aspx
在這裏,awk用來分別從makeconf.awk和makeopt.awk生成conf.c和options.c源文件,注意,在修改了linux.mk

或是添加了alias之後要先把之前make失敗時生成的conf.c和options.c刪除,否則會報錯

4.進行了上述的修改後,回到WKDIR,運行make,ok,沒有報錯,編譯成功了

之後,如果你要利用apue的lib,編譯運行自己的代碼,必須在編譯時加上-I/home/se/apue.2e/include選項,

在連接時加上-L/home/se/apue.2e/lib source.c /home/se/apue.2e/lib/libapue.a選項,這樣你就可以利

用apue提供的想err_sys等函數了^_^


/*******************************************************************************
*第三種  不建議使用
*
*******************************************************************************/




unix環境高級編程編譯方法

    這裏要談到的一個問題就是該書中的源代碼編譯的問題。此書中差不多每個歷程中,都會有這樣一行源

碼:

    #include "ourhdr.h"
      
    在第二版中改爲:
    #include "apue.h"

        這個頭文件是作者把把每個例程中常用的標準頭文件,一些常用的出錯處理函數(err_**()之類

的函數)和一些常用的宏定義給整理在一個頭文件中。這個可以省去在每個例程中錄入較多的重複代碼,這

樣可以減少每個例程的長度。但是,這樣就給讀者帶來了不少麻煩。因爲我們還要去搞明白如何把這個頭文

件編譯,然後做成庫文件,添加到我們的系統中。特別讀於初學者,本來滿懷信心的,結果在編譯第一個程

序的時候就出現了問題。我也沒有搞明白如何把 "ourhdr.h"靜態的編譯到系統中。

        不過,不明白如何使用"ourhdr.h"這個頭文件,並不會影響我們學習APUE,也不會影響我們編譯和

運行每一個例程。其實,簡單的想一下,如果一個 C程序要能順利的編譯和運行,除了我們要語法正確等方

面外,最根本的是要保證我們程序中所調用的函數以及宏等等都要有完整的來源,也就是必須包含所有調用

函數和宏所在的頭文件。對於一個具體的源程序,如果我們正確的包含了頭文件,那麼剩下的就是程序本生

語法方面應該注意的事項。

        如何確定系統調用函數包含在那個頭文件中呢?這在Unix/Linux系統下並非一件難事。Unix/Linux

下命令man可以幫助我們找到。man命令不僅可以幫助我們查找一般命令的用法,同時提供不同層次的幫助諸

如系統調用或者管理員級別的命令等等(譬如FreeBSD6.1中,man 1是用戶專用手冊,man 2是系統調用,

man 3是庫函數查詢等等)。

        下面我們就以APUE書中程序1-1 (實現ls命令部分功能)爲例,來說明如何將書中的程序改編成全

部使用標準頭文件的程序。其中,操作系統用的是FreeBSD6.1,經過相應的修改可以在書中所說的幾個Unix

系統及Linux系統中運行,我也曾在Debian Linux下成功編譯和運行該程序。書中1-1.c的原始代碼如下:

    #include <sys/types.h>
    #include <dirent.h>
    #include "ourhdr.h"

    int
    main(int argc, char *argv[])
    {
        DIR                *dp;
        struct dirent    *dirp;

        if (argc != 2)
            err_quit("usage: ls directory_name");

        if ((dp = opendir(argv[1])) == NULL)
            err_sys("can't open %s", argv[1]);
        while ((dirp = readdir(dp)) != NULL)
            printf("%s"n", dirp->d_name);

        closedir(dp);
        exit(0);
    }

        從書後面的附錄中可以看到"ourhdr.h"的內容比較多,包含了比較多的常用頭文件,一些宏定義和

一些常用函數和出錯函數的定義。其實,對於每一個具體的程序,我們只需要找到該程序中用到的頭文件即

可。

        該1-1.c中所用到的系統函數調用有:opnedir(),readdir(),printf(),closedir()和exit()。
    其中,對於常用的函數prinft()和exit(),它們所在的頭文件一般都知道,分別是<stdio.h>和< 

stdlib.h>。而對於opnedir (),readdir()和closedir(),我們可以通過man opendir,man readdir,man 

closedir得到這三個關於目錄操作的函數所在的頭文件都是:<sys/types.h>和<dirent.h>。這兩個頭文件

在源程序中也已經列出。

        其次,1-1.c中還用到了作者自定義的兩個函數:err_quit()和err_sys()。這兩個函數主要使用來

進行出錯處理的。當然,使用這兩個函數對錯誤信息的處理是比較完善的。但是,作爲我們學習來講,瞭解

程序的核心功能是首要的,我們可以將出錯處理簡化一點,即當遇到錯誤的時候,我們只簡單的使用

printf()函數來提示一下有錯誤發生。當然,用printf()來進行出錯處理並不是一種很合理的方法,而且往

往我們看不到更關鍵的錯誤信息,但對於我們僅僅作爲學習來用還是可以接受的。畢竟我們要理解的核心部

分是程序的功能實現,出錯處理在於其次。

       通過以上的說明,我們可以將1-1.c修改爲如下內容:

    #include <sys/types.h>
    #include <dirent.h>
    #include <stdio.h>
    #include <stdlib.h>
    int main(int argc, char* argv[])
    {
        DIR *dp;
        struct dirent *dirp;
       
        if(argc != 2)
        {
            printf("You need input the directory name."n");
            exit(1);  
        }
       
        if((dp = opendir(argv[1])) == NULL)
        {
            printf("cannot open %s"n", argv[1]);
            exit(1);   
        }

        while ((dirp = readdir(dp)) != NULL)
            printf("%s"n", dirp->d_name);


        closedir(dp);

        exit(0);
    }

        這樣修改後的程序已經與作者的頭文件"ourhdr.h"沒有關係,可以單獨的進行編譯。我使用的是

root用戶,執行命令:

    # gcc 1-1.c  //生成目標文件a.out
    或者
    # gcc -o 1-1 1-1.c  //生成目標文件1-1

        沒有任何錯誤和警告,說明編譯成功。這時我們執行生成的目標文件:

    # ./a.out /home
    或者
    # ./1-1 /home

        則會列出/home路徑下的所有文件,包括目錄(.)和(..)。

        通過這樣的方法,基本上我們可以將該書中所有的例程修改成不包含"ourhdr.h"的程序。這樣,我

們就可以單獨的編譯每一個例程,而不用顧及作者所給的雜湊的頭文件。同時這種比較笨的方法,反而有利

於幫助我們瞭解不同系統調用所對應的頭文件,對於學習來說,這應該是一件好事。


/****************************************************************************************************************

*   其他1

******************************************************************************************************************/


apue.h 是作者自定義的一個頭文件,並不是Unix/Linux系統自帶的,此頭文件包括了Unix程序所需的常用頭文件及作者Richard自己寫的出錯處理函數。所以在默認情況下,gcc在編譯時是讀不到這個頭文件的。

先在這個網站 http://www.apuebook.com/src.tar.gz 下載tar.gz格式的源碼包,然後解壓至某個目錄,比如說/home/godsoul/下,然後進入目錄apue.2e,把文件 Make.defines.linux 中的 WKDIR=/home/xxx/apue.2e 修改爲 WKDIR=/home/godsoul/apue.2e ,然後再進入apue.2e目錄下的std目錄,打開linux.mk,將裏面的nawk全部替換爲awk,如果是用的vi/vim編輯器,可以使用這個 命令  :1.$s/nawk/awk/g (注意前面有冒號)
然後在此目錄下運行make命令,即回到 /home/godsoul/apue.2e 目錄在終端中輸入 “./make” (不含引號)

然後把 /home/godsoul/apue.2e/inlcude 目錄下的 apue.h 文件和位於 /home/godsoul/apue.2e/lib 目錄下的 error.c 文件都複製到 /usr/include 目錄下,apue.2e/lib/libapue.a 到/usr/lib/和 /usr/lib64下。注意複製這文件你需要有root權限。之所以要這樣做,是因爲gcc在鏈接頭文件時會到 /usr/include 這個目錄下尋找需要的頭文件,若找不到則報錯。

最終還要編輯一下複製過來的 apue.h 文件
在最後一行 #endif 前面添加一行 #include “error.c”

然後進入apue.2e/std 目錄,編輯linux.mk。修改裏面所有的nawk爲awk。

這樣就不會報錯了。

還又可能遇到的問題如下:

如果出現stropts.h找不到的情況,則下載glibc-2.11,解壓縮
cp ./glibc-2.11/streams/stropts.h /usr/include
cp ./glibc-2.11/bits/stropts.h /usr/include/bits
cp ./glibc-2.11/sysdeps/x86_64/bits/xtitypes.h /usr/include/bits

在我的機器上編譯時,提示ARG_MAX未定義,可以這麼修改。
在apue.2e/include/apue.h中添加一行:
#define ARG_MAX 4096
打開apue.2e/threadctl/getenv1.c 和apue.2e/threadctl/getenv3.c,添加一行:
#include “apue.h”

改好後make clean再重新make

2. 使用apue.h文件和libapue.a庫。
假定/tmp下有一個文件:threadid.c,內容如下(apue線程章節的例子):
#include <apue.h>
#include <pthread.h>

pthread_t ntid;

void
printids(const char *s)
{
pid_t           pid;
pthread_t       tid;

pid = getpid();
tid = pthread_self();
printf(“%s pid %u tid %u (0x%x)\n”, s, (unsigned int)pid,
(unsigned int)tid, (unsigned int)tid);
}

void *
thr_fn(void *arg)
{
printids(“new thread: “);
return((void *)0);
}

int
main(void)
{
int             err;

err = pthread_create(&ntid, NULL, thr_fn, NULL);
if (err != 0)
err_quit(“can’t create thread: %s\n”, strerror(err));
printids(“main thread:”);
sleep(1);
exit(0);
}

使用如下命令編譯:
cc -o threadid threadid.c -lapue -lpthread
可以運行一下:
dan@dan-laptop:/tmp$ ./threadid
new thread:  pid 17490 tid 816015696 (0x30a36950)
main thread: pid 17490 tid 823949040 (0x311c76f0)

3. 編譯《UNP》
這個稍微麻煩些。

http://www.unpbook.com/unpv13e.tar.gz

我們首先產生一個目錄,以後自己的代碼就敲在這個目錄裏。
mkdir /home/dan/study/unp

仍然是下載到/home/dan/download/,解壓縮,進入目錄
cd /home/dan/download/unpv13e/
README文件中說的很詳細:
========================================
Execute the following from the src/ directory:

./configure    # try to figure out all implementation differences

cd lib         # build the basic library that all programs need
make           # use “gmake” everywhere on BSD/OS systems

cd ../libfree  # continue building the basic library
make

cd ../libroute # only if your system supports 4.4BSD style routing sockets
make           # only if your system supports 4.4BSD style routing sockets

cd ../libxti   # only if your system supports XTI
make           # only if your system supports XTI

cd ../intro    # build and test a basic client program
make daytimetcpcli
========================================
這裏只編譯lib下的文件,這樣可以產生libunp.a,複製這個靜態庫到/usr/lib/和/usr/lib64/
如果提示:
unp.h:139: error: conflicting types for ‘socklen_t’
/usr/include/bits/socket.h:35: error: previous declaration of ‘socklen_t’ was here
需要註釋掉當前目錄中unp.h的第139行。
複製libunp.a到系統目錄:
root@dan-laptop:/home/dan/download/unpv13e/lib# cp ../libunp.a /usr/lib
root@dan-laptop:/home/dan/download/unpv13e/lib# cp ../libunp.a /usr/lib64/

4.使用unp.h和libunp.a
如果直接複製unpv13e/lib/unp.h到/usr/include,那麼在別的目錄編譯書上代碼時,很可會得到類似下面的錯誤:
In file included from daytimetcpsrv1.c:1:
/usr/include/unp.h:227: error: redefinition of ‘struct sockaddr_storage’
In file included from daytimetcpsrv1.c:1:
/usr/include/unp.h:249:30: error: ../lib/addrinfo.h: No such file or directory
/usr/include/unp.h:263: error: redefinition of ‘struct timespec’
/usr/include/unp.h:363: error: conflicting types for ‘gai_strerror’
/usr/include/netdb.h:647: error: previous declaration of ‘gai_strerror’ was here
/usr/include/unp.h:367: error: conflicting types for ‘getnameinfo’
/usr/include/netdb.h:653: error: previous declaration of ‘getnameinfo’ was here
/usr/include/unp.h:371: error: conflicting types for ‘gethostname’
/usr/include/unistd.h:857: error: previous declaration of ‘gethostname’ was here
/usr/include/unp.h:387: error: conflicting types for ‘inet_ntop’
/usr/include/arpa/inet.h:65: error: previous declaration of ‘inet_ntop’ was here
/usr/include/unp.h:395: error: conflicting types for ‘pselect’
/usr/include/sys/select.h:121: error: previous declaration of ‘pselect’ was here
daytimetcpsrv1.c: In function ‘main’:
daytimetcpsrv1.c:9: error: ‘MAX_LINE’ undeclared (first use in this function)
daytimetcpsrv1.c:9: error: (Each undeclared identifier is reported only once
daytimetcpsrv1.c:9: error: for each function it appears in.)
dan@dan-laptop:~/study/unp/4rmf/usr/include/unp.hcd/home/dan/study/unpconfig.hunp.hdan@danlaptop: /study/unpcp /home/dan/download/unpv13e/config.h .
dan@dan-laptop:~/study/unp$ cp /home/dan/download/unpv13e/lib/unp.h .
修改unp.h,
#include “../config.h”改成 #include “config.h”
添加一行:
#define MAX_LINE 2048
練習書上代碼時,在unp目錄下建立相應的章節目錄,文件中添加一行:
#include “../unp.h”
編譯時鏈接unp庫就可以了。

以第四章的daytimetcpsrv1.c爲例:
dan@dan-laptop:~/study/unp/4pwd/home/dan/study/unp/4dan@danlaptop: /study/unp/4 cat daytimetcpsrv1.c
#include “../unp.h”
#include <time.h>

int main(int argc, char **argv)
{
int    listenfd, connfd;
socklen_t    len;
struct sockaddr_in    servaddr, cliaddr;
char    buff[MAX_LINE];
time_t    ticks;

listenfd = Socket(AF_INET, SOCK_STREAM, 0);

bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(13);

Bind(listenfd, (SA *)&servaddr, sizeof(servaddr));

Listen(listenfd, LISTENQ);

for (;;) {
len = sizeof(cliaddr);
connfd = Accept(listenfd, (SA *)&cliaddr, &len);
printf(“connection from %s, port %d\n”,
Inet_ntop(AF_INET, &cliaddr.sin_addr, buff, sizeof(buff)),
ntohs(cliaddr.sin_port));

ticks = time(NULL);
snprintf(buff, sizeof(buff), “%.24s\r\n”, ctime(&ticks));
Write(connfd, buff, strlen(buff));

Close(connfd);
}
}
編譯:
cc -o daytimetcpsrv1 daytimetcpsrv1.c -lunp
運行一下:
root@dan-laptop:/home/dan/study/unp/4# ./daytimetcpsrv1 &
[1] 22106
root@dan-laptop:/home/dan/study/unp/4#
root@dan-laptop:/home/dan/study/unp/4# ./daytimetcpcli
usage: a.out <IPaddress>
root@dan-laptop:/home/dan/study/unp/4# ./daytimetcpcli 127.0.0.1
connection from 127.0.0.1, port 42064
Fri Aug 21 23:03:56 2009
root@dan-laptop:/home/dan/study/unp/4# netstat -nt
Active Internet connections (w/o servers)
Proto Recv-Q Send-Q Local Address           Foreign Address         State
tcp        0      0 127.0.0.1:13            127.0.0.1:42064         TIME_WAIT




/*****************************************************************************************************

*   其他2

*****************************************************************************************************/


提示 "錯誤:apue.h:沒有那個文件或目錄".

很是受打擊,果斷google解決之。

apue.h是作者自定義的一個頭文件,包括程序所需的常用頭文件及出錯處理函數。所以因該將它放入系統頭文件中(Linux下是 /usr/include),這樣gcc編譯器就可以找到它了。

http://www.linuxidc.com/Linux/2013-01/77467.htm下載src.tar.gz包,然後解壓至電腦中的某個目錄,比如我的是在/home/user/下

user@user-desktop:~$ sudo tar xzf src.tar.gz

進入解壓目錄apue.2e,修改 Make.defines.linux中的WKDIR=/home/xxx/apue.2e,爲WKDIR=/home/user/apue.2e

進入std目錄,修改linux.mk,將裏面的nawk全部改爲awk。

複製apue.h和error.c

將apue.h和error.c兩個文件copy到/usr/include目錄下。(apue.h位於 your_apue_path/inlcude ; error.c位於your_apue_path/lib )

以我的路徑爲例:

user@user-desktop:/usr/include$ cp /home/user/apue.2e/inlcude/apue.h apue.h
user@user-desktop:/usr/include$ cp /home/user/apue.2e/lib/error.c error.c (實現apue.h中的出錯處理函數)

修改apue.h

在最後一行#endif  前面添加一行 #include "error.c"

貌似這樣就OK了。

UNIX環境高級編程中文第二版PDF高清版 http://www.linuxidc.net/thread-2063-1-1.html



/******************************************************************

****** Ubuntu系統的移植編譯

*******************************************************************


按照上面的諸多方法變異會出現下面兩個錯誤:

                        make[2]: *** [printd.o] 錯誤 1
                        make[2]:正在離開目錄 `/tmp/apue.2e/ipp'
                        make[1]: *** [linux] 錯誤 1
                        make[1]:正在離開目錄 `/tmp/apue.2e'
                        make: *** [all] 錯誤 2

錯誤的原因:

1. 在apue.2e/ipp.h中定義了一個宏定義status和/usr/include/i386-linux-gnu/bits/timex.h中的成員status衝突

2. ARG_MAX未定義


解決辦法:

1. 修改這個apue.2e/ipp/ipp.h文件中的宏名稱,例如改爲Status;然後將apue.2e/ipp/printd.c中977行的 hp->status 改爲hp->Status;

2. 在apue.2e/include/apue.h中添加一行:  #define ARG_MAX 4096   

3. 在路徑下的文件apue.2e/threadctl/getenv1.c 和apue.2e/threadctl/getenv3.c,添加一行:#include "apue.h"


/*   編輯成庫文件以供其他程序使用  */

1、修改相應平臺的文件,將Make.defines.linux文件中的 WKDIR=/home/your_dir/apue.2e, 其中/home/your_dir爲Apue.2e包的路徑;

2.  使用make命令在上述路徑下編譯通過;

3. 把apue的頭文件和庫文件放入系統,具體如下:使用sudo su使用root權限,複製文件到系統默認搜索路徑下

            cp ~/apue.2e/include/apue.h /usr/include
    cp ~/apue.2e/lib/libapue.a /usr/lib/

             其中~爲/home/your_dir

4.  這樣庫文件就編譯和設置好了,使用APUE書中帶#include "apue.h"的程序,只需引用此庫即可

        gcc  source.c -o excutename  -lapue

       如:gcc Hello.c -0  Hello -lapue      Hello.c 爲要編譯的程序名,Hello爲可執行文件名



發佈了11 篇原創文章 · 獲贊 13 · 訪問量 17萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章