求助-读取文件出错,最后一行重复输出[已解决-都是feof惹的祸]

时间:2008-01-16 11:14:27  来源:  作者:

写了一个程序,可以格式化的从文件中读出自己想要的字段,然后分别显示在屏幕上,虽然方法可能傻了点,不过还算好用,后来在测试的时候,突然发现了一个问题,不知道为什么,当文件读到最后时,并没有结束,而是将最后一行字串又打了一遍,我试过将fgets换为fwrite,虽然不会再多打出一行,但是因为文件的长度不确定,结果输出的都是乱码,看了看书上的例子,好象只有定义结构体之后格式化输出才能正确读取,难道没有别的办法了么....

__________________________________________________________________
运行环境:

FC6/gcc4.0.1

程序如下:


#include <stdio.h>
#include <string.h>

void file_format_read(void)
{
        FILE *filep;

        char read_log[100]="";

        char select_file_name[20]="";
        char select_file_date[20]="";
        char select_file_length[20]="";
        char select_file_size[20]="";

        filep=fopen("file_format.txt","r");
      if(!filep)
              printf("open file error!n");
              printf("open file ok!n");
        while(!feof(filep))
       {
                fgets(read_log,100,filep);

                        strncpy(select_file_name,read_log,16);
                        strncpy(select_file_date,read_log+17,16);
                        strncpy(select_file_length,read_log+34,strcspn(read_log,"#")-strcspn(read_log,"@")-1);
                        strncpy(select_file_size,strchr(read_log,'#')+1,strcspn(read_log,"$")-strcspn(read_log,"#")-1);

                        printf("fread = %sn",read_log);
                        printf("select_file_name = %sn",select_file_name);
                        printf("select_file_date = %sn",select_file_date);
                    
                       printf("select_file_length = %sn",select_file_length);
                       printf("select_file_size = %sn",select_file_size);
                       printf("**********************************n");
                        strcpy(select_file_name,"");
                        strcpy(select_file_date,"");
                        strcpy(select_file_length,"");
                        strcpy(select_file_size,"");
          }
        fclose(filep);
}

int main()
{
        file_format_read();
        return 1;
}



用来读的文件:

名称:file_format.txt

内容:

200706201421.avi 2007-06-20 14:21@58分钟#45M%
200706201422.avi 2007-06-20 14:22@158分钟#45M%
200706201423.avi 2007-06-20 14:23@58分钟#145M%
200706201424.avi 2007-06-20 14:24@58分钟#45M%
200706201425.avi 2007-06-20 14:25@258分钟#245M%
200706201426.avi 2007-06-20 14:26@58分钟#45M%
200706201427.avi 2007-06-20 14:27@58分钟#45M%
200706201428.avi 2007-06-20 14:28@2258分钟#2145M% 
200706201429.avi 2007-06-20 14:29@58分钟#45M%

[ 本帖最后由 封神 于 2007-7-3 09:10 编辑 ]



 福瑞哈哥 回复于:2007-07-02 20:45:53

记得这种把feof当作循环测试条件,造成最后一行打印两次的问题在论坛已经多次出现了。
一句话:忘掉feof,直接用fgets。


 JohnBull 回复于:2007-07-02 20:48:47

可以用feof,但必须记住:当文件刚刚读到文件尾时,feof不会返回true。只有在文件尾部再次进行一次读操作,feof才会返回真。

这与底层系统调用相关。因为只有read返回一次0值,进程才能知道文件结束了。


 MMMIX 回复于:2007-07-02 21:50:58

引用:原帖由 福瑞哈哥 于 2007-7-2 20:45 发表
记得这种把feof当作循环测试条件,造成最后一行打印两次的问题在论坛已经多次出现了。
一句话:忘掉feof,直接用fgets。 


呵呵,看到标题我就猜到是 feof 惹的祸,一看果然如此。


 flw 回复于:2007-07-02 21:54:44

feof() 就是蛇足,见一次说一次。


 MMMIX 回复于:2007-07-02 21:57:04

引用:原帖由 flw 于 2007-7-2 21:54 发表
feof() 就是蛇足,见一次说一次。 


果然又是这话 :em06::em06::em06:


 封神 回复于:2007-07-02 23:17:12

受教了,那么将判断条件改为

while(fgets(read_log,100,filep))
       
就可以了么?

家里没有编译器,明天到公司试试.


 封神 回复于:2007-07-02 23:19:34

话说回来,既然这个函数有漏洞,为什么新版本的GCC没有修正这个错误呢,反尔将很多有用字符串处理函数去掉了不少.


 MMMIX 回复于:2007-07-02 23:26:25

引用:原帖由 封神 于 2007-7-2 23:19 发表
话说回来,既然这个函数有漏洞,为什么新版本的GCC没有修正这个错误呢,反尔将很多有用字符串处理函数去掉了不少. 


谁说这个是错误/漏洞?


 封神 回复于:2007-07-03 08:38:54

这个不算是漏洞么?


 ypxing 回复于:2007-07-03 08:44:26

这个不是漏洞,
只是多少有些容易产生误导

引用:原帖由 封神 于 2007-7-3 08:38 发表
这个不算是漏洞么? 




 ivhb 回复于:2007-07-03 08:52:29

引用:原帖由 封神 于 2007-7-2 20:25 发表
写了一个程序,可以格式化的从文件中读出自己想要的字段,然后分别显示在屏幕上,虽然方法可能傻了点,不过还算好用,后来在测试的时候,突然发现了一个问题,不知道为什么,当文件读到最后时,并没有结束,而是将最后一行字 ... 



while (!feof(fp)) {
  buf[0] = 0x00;
  fgets(buf, sizeof buf, fp);
  if (buf[0] == 0x00)
     break;  // ferror(fp) 或者 feof(fp)
  ...
}
if (ferror(fp)) { // 对应break出错时候蹦出来的情况
   
}


当然,还是像他们说的一样,直接fgets放在while 里最好了,没有必要的。

[ 本帖最后由 ivhb 于 2007-7-3 08:53 编辑 ]


 封神 回复于:2007-07-03 09:09:08

收到,谢了


 福瑞哈哥 回复于:2007-07-03 09:28:15

引用:原帖由 ivhb 于 2007-7-3 08:52 发表


while (!feof(fp)) {
  buf[0] = 0x00;
  fgets(buf, sizeof buf, fp);
  if (buf[0] == 0x00)
     break;  // ferror(fp) 或者 feof(fp)
  ...
}
if (ferror(fp)) { // 对应break出错时候蹦出来的情 ... 



没有测试过你的程序,但是感觉有问题。


 MMMIX 回复于:2007-07-03 09:38:49

引用:原帖由 封神 于 2007-7-3 08:38 发表
这个不算是漏洞么? 


也许在设计之初算是,但是现在这个已经写入标准,那么就不再是 bug,而是标准了 :mrgreen:


 ivhb 回复于:2007-07-03 12:25:41

引用:原帖由 福瑞哈哥 于 2007-7-3 09:28 发表


没有测试过你的程序,但是感觉有问题。 




:) 如果是磁盘文件来说,这么做不会有什么问题。
因为磁盘文件,ferror几乎不可能为错。只要你打开成功的话。
对于ferror来说,因为没有规定
fgets失败,buf是否会被复写。因此这个是未定义的。
但是到达文件结束,buf会left unchanged。
因此,这么做几乎是等价的吧。


 封神 回复于:2007-07-03 15:06:53

最终选择在while中写fgets,对于我个人来说比较好理解,再次谢谢大家。



引用:原帖由 MMMIX 于 2007-7-3 09:38 发表

也许在设计之初算是,但是现在这个已经写入标准,那么就不再是 bug,而是标准了 :mrgreen: 




强权标准啊,根本就不好用,还要强迫大家接受,修正这么一个函数又不会死人,真不知道为什么那些家伙不改


 思一克 回复于:2007-07-03 16:10:27

LZ,你应该判断fgets()为NULL,不判断而继续使用buf的内容,自己的程序是不对的。

foef(stream)针对的是”流“, 用一个indicator(指示)表明文件尾,而feof判断这个指示是否设置,而不是判断当前SEEK数值是否到尾。
seek值到尾了对于“流”不一定是是文件尾,因为“流”是会不断增长的。比如pipe, sockfd等。

不是BUG,不要想象这么多年了编制库函数的人都愚不可及。


NAME
       clearerr, feof, ferror, fileno - check and reset stream status

SYNOPSIS
       #include <stdio.h>

       void clearerr(FILE *stream);
       int feof(FILE *stream);
       int ferror(FILE *stream);
       int fileno(FILE *stream);

DESCRIPTION
       The function clearerr clears the end-of-file and error indicators for the stream pointed to by stream.

       The  function  feof tests the end-of-file indicator for the stream pointed to by stream, returning non-zero if it is
       set.  The end-of-file indicator can only be cleared by the function clearerr.

       The function ferror tests the error indicator for the stream pointed to by stream, returning non-zero if it is  set.
       The error indicator can only be reset by the clearerr function.

       The function fileno examines the argument stream and returns its integer descriptor.

       For non-locking counterparts, see unlocked_stdio(3).

ERRORS
       These  functions  should not fail and do not set the external variable errno.  (However, in case fileno detects that
       its argument is not a valid stream, it must return -1 and set errno to EBADF.)

CONFORMING TO
       The functions clearerr, feof, and ferror conform to X3.159-1989 (????&#8364;?ANSI C????&#8364;?.

SEE ALSO
       open(2), unlocked_stdio(3), stdio(3)


 ivhb 回复于:2007-07-03 16:48:44

引用:原帖由 思一克 于 2007-7-3 16:10 发表
LZ,你应该判断fgets()为NULL,不判断而继续使用buf的内容,自己的程序是不对的。

foef(stream)针对的是”流“, 用一个indicator(指示)表明文件尾,而feof判断这个指示是否设置,而不是判断当前SEEK数值是否 ... 



一直很欣赏  思一克, 不会放过任何一个细小的细节。
见你说了这么多,忍不住顺便也说一下,对于pipe,sock,本身是不可seek的,这是其一;
另外,如果当前的pipe/sock里没有可读的数据,fgets会一直等待,根本不会返回:),因此
也不会给判断buf的机会:) 除非pipe/sock另外一写入端close或者读到足够的字节,或者被信号打断.
如果你能进入到判定buf的时候,一定是1. 读到足够的字节数,2不足,但是已经到文件尾。
如果buf[0] == 0x00, 那么,一定是到了文件尾:)
考虑到低速设备容易被信号打断,因此,一上来就用了,如果对于磁盘文件 ...

[ 本帖最后由 ivhb 于 2007-7-3 16:58 编辑 ]


 思一克 回复于:2007-07-03 17:02:40

可能我用直接用sock, pipe做例子不对。

但可以想,LIB库,用一个指示来判断,应该有其道理的,虽然会引起误导。
根据是什么,就是这么成熟的使用多年的库,有是这么简单的问题,不可能有错误。而且man page中说的也是那样判断eof的指示是否设置,而不是判断目前是否读在最后一个字节。至于指示怎么被设置没有说。

再有,且不说以上的,就是程序中判断错误,也应该用最后那个操作fgets()来判断,否则直接fgets后就利用buf内容是绝对不对的,因为fgets返回错误的情况呢?




引用:原帖由 ivhb 于 2007-7-3 16:48 发表


一直很欣赏  思一克, 不会放过任何一个细小的细节。
见你说了这么多,忍不住顺便也说一下,对于pipe,sock,本身是不可seek的,这是其一;
另外,如果当前的pipe/sock里没有可读的数据,fgets会一直等待, ... 




 福瑞哈哥 回复于:2007-07-03 17:11:00

引用:原帖由 ivhb 于 2007-7-3 08:52 发表


while (!feof(fp)) {
  buf[0] = 0x00;
  fgets(buf, sizeof buf, fp);
  if (buf[0] == 0x00)
     break;  // ferror(fp) 或者 feof(fp)
  ...
}
if (ferror(fp)) { // 对应break出错时候蹦出来的情 ... 



是的,你的程序没问题。我上午只看到了if(buf[0]==0x00)这句,没看到你上面buf[0]==0x00这句,觉得这样写程序很没有必要。fgets有返回值直接使用就可以了,逻辑越多别人越不容易理解。


 MMMIX 回复于:2007-07-03 19:01:39

引用:原帖由 封神 于 2007-7-3 15:06 发表
最终选择在while中写fgets,对于我个人来说比较好理解,再次谢谢大家。






强权标准啊,根本就不好用,还要强迫大家接受,修正这么一个函数又不会死人,真不知道为什么那些家伙不改 


为了向后兼容。


 whyglinux 回复于:2007-07-03 23:46:05

>> 都是feof惹的祸
>> 这个不算是漏洞么?

是漏洞。只不过是你写的程序中存在的漏洞,并不是 feof 函数的过错。产生漏洞的原因是你还没有掌握 feof 函数的正确使用方法,而不是什么“都是feof惹的祸”。

>> 强权标准啊,根本就不好用,还要强迫大家接受,修正这么一个函数又不会死人,真不知道为什么那些家伙不改

首先要想清楚你想要他们改什么,怎么改?

feof 函数的声明和行为已经是包括 ANSI C 标准在内的很多标准规定的函数了。即使是 C 标准委员会的人想对它进行修改也已经是不可能的事了,况且实际上也无需修改。

================

其实正确使用 feof 函数很容易,只要记住一个原则就是了:“先读文件后用 feof 判断”。你上面使用 feof 函数的程序都没有做到这一点,而是先用 feof 函数判断、然后再读文件,所以是不正确的。可根据上面的这个原则修改你的程序,使之正确。


 思一克 回复于:2007-07-04 09:01:37

whyglinux说的准确。漏洞是LZ的程序,而不是feof.

引用:原帖由 whyglinux 于 2007-7-3 23:46 发表 [url=http://bbs.chinaunix.net/redirect.php?goto=findpost&pid=7001994&ptid=956786]
>> 都是feof惹的祸
>> 这个不算是漏洞么?

是漏洞。只不过是你写的程序中存在的漏洞,并不是 feof 函数的过错。产生漏洞的原因是你还没有掌握 feof 函数的正确使用方法,而不是什么“都是feof惹的祸”。

 ... 




 while(1) 回复于:2007-07-04 09:41:48

“先读文件后用 feof 判断”

:funk: 原来如此


 福瑞哈哥 回复于:2007-07-04 09:49:18

每次看到有人用feof我都会感到同情!
不管feof有没有用,如果使用fgets而不判断返回值都是错误!
如果判断了fgets的返回值,也就没有必要使用feof了。

引用:原帖由 while(1) 于 2007-7-4 09:41 发表 [url=http://bbs.chinaunix.net/redirect.php?goto=findpost&pid=7002848&ptid=956786]
“先读文件后用 feof 判断”

:funk: 原来如此 




 ivhb 回复于:2007-07-04 10:26:33

引用:原帖由 while(1) 于 2007-7-4 09:41 发表 [url=http://bbs.chinaunix.net/redirect.php?goto=findpost&pid=7002848&ptid=956786]
“先读文件后用 feof 判断”

:funk: 原来如此 




假定进程A读取文件已经到了文件尾,feof测试为真。
如果此时进程B追加A正在读取的文件,你觉得,如果A不发生进一步的读/写动作,
接着测试feof,你觉得是真还是假?

feof仅仅是个宏,仅仅读取了上次读/写动作发生后置于FILE 结构的一个标志位。
只有进一次的读/写动作才会更新那个标志位。


 思一克 回复于:2007-07-04 10:31:36

应该仍然是真。因为INDICATOR的缘故
我没有具体测试。

引用:原帖由 ivhb 于 2007-7-4 10:26 发表 [url=http://bbs.chinaunix.net/redirect.php?goto=findpost&pid=7003216&ptid=956786]



假定进程A读取文件已经到了文件尾,feof测试为真。
如果此时进程B追加A正在读取的文件,你觉得,如果A不发生进一步的读/写动作,
接着测试feof,你觉得是真还是假?

feof仅仅是个宏,仅仅读取了上次 ... 




 while(1) 回复于:2007-07-04 10:58:00

引用:原帖由 福瑞哈哥 于 2007-7-4 09:49 发表 [url=http://bbs.chinaunix.net/redirect.php?goto=findpost&pid=7002918&ptid=956786]
每次看到有人用feof我都会感到同情!
不管feof有没有用,如果使用fgets而不判断返回值都是错误!
如果判断了fgets的返回值,也就没有必要使用feof了。

 


fgets返回NULL的情况就表示一定是文件读完了么?


 福瑞哈哥 回复于:2007-07-04 11:04:13

难道还有其他选择?
fgets返回null表示这个文件对程序来说是读完了。基本上就是这样,有其他情况99%你也不会处理的。

引用:原帖由 while(1) 于 2007-7-4 10:58 发表 [url=http://bbs.chinaunix.net/redirect.php?goto=findpost&pid=7003404&ptid=956786]

fgets返回NULL的情况就表示一定是文件读完了么? 




 while(1) 回复于:2007-07-04 11:04:44

引用:原帖由 ivhb 于 2007-7-4 10:26 发表 [url=http://bbs.chinaunix.net/redirect.php?goto=findpost&pid=7003216&ptid=956786]



假定进程A读取文件已经到了文件尾,feof测试为真。
如果此时进程B追加A正在读取的文件,你觉得,如果A不发生进一步的读/写动作,
接着测试feof,你觉得是真还是假?

feof仅仅是个宏,仅仅读取了上次 ... 


我的理解是:fgets读到文件最后一行的时候(返回不是NULL) ,feof测试为假,再fgets一次(返回为NULL),再feof测试为真。对否?
你的问题我觉得是 如果在A中用feof是真,在B中用feof是假,对否?


 while(1) 回复于:2007-07-04 11:06:51

引用:原帖由 福瑞哈哥 于 2007-7-4 11:04 发表 [url=http://bbs.chinaunix.net/redirect.php?goto=findpost&pid=7003435&ptid=956786]
难道还有其他选择?
fgets返回null表示这个文件对程序来说是读完了。基本上就是这样,有其他情况99%你也不会处理的。

 


我的意思是fgets出错的情况有可能么?这时候fgets是什么反应?好奇


 福瑞哈哥 回复于:2007-07-04 11:08:21

看man page, 

gets() and fgets() return s on success, and NULL on error or when  end
       of file occurs while no characters have been read.

不要将问题考虑的那样复杂


理论上fgets确实有出错的可能,但是谁又会去处理它呢,又如何处理呢?至今阅码无数,尚无见识过处理fgets错误的代码。

引用:原帖由 while(1) 于 2007-7-4 11:06 发表 [url=http://bbs.chinaunix.net/redirect.php?goto=findpost&pid=7003452&ptid=956786]

我的意思是fgets出错的情况有可能么?这时候fgets是什么反应?好奇 



[ 本帖最后由 思一克 于 2007-7-5 15:20 编辑 ]


 MMMIX 回复于:2007-07-04 11:42:53

引用:原帖由 福瑞哈哥 于 2007-7-4 11:08 发表 [url=http://bbs.chinaunix.net/redirect.php?goto=findpost&pid=7003459&ptid=956786]
理论上fgets确实有出错的可能,但是谁又会去处理它呢,又如何处理呢?至今阅码无数,尚无见识过处理fgets错误的代码。

 


不会这么夸张吧?

while(fgets(..., stream)){
        /* ... */
}

if(ferror(stream)){
        /* ... */
}

这种例子一堆一堆的。

[ 本帖最后由 MMMIX 于 2007-7-4 11:44 编辑 ]


 清汤挂面 回复于:2007-07-05 11:31:10

每次读之前memset一下buffer


 福瑞哈哥 回复于:2007-07-05 11:32:43

引用:原帖由 MMMIX 于 2007-7-4 11:42 发表 [url=http://bbs.chinaunix.net/redirect.php?goto=findpost&pid=7003684&ptid=956786]

不会这么夸张吧?

while(fgets(..., stream)){
        /* ... */
}

if(ferror(stream)){
        /* ... */
}

这种例子一堆一堆的。 



我是没见过。 if(ferror(stream)) {}里面写什么?


 xiaomiao 回复于:2007-07-05 11:46:05

不如用C++的ifstream + while循环

ifstream in("/tmp/test.txt",ios::in);
string temp;

 while(getline(in,temp,'n'))  
 {
    //.....
}


 MMMIX 回复于:2007-07-05 12:24:07

引用:原帖由 福瑞哈哥 于 2007-7-5 11:32 发表 [url=http://bbs.chinaunix.net/redirect.php?goto=findpost&pid=7009049&ptid=956786]


我是没见过。 if(ferror(stream)) {}里面写什么? 


来个完整的例子吧,APUE2 的 Figure 15.11

#include "apue.h"
#include <sys/wait.h>

#define PAGER   "${PAGER:-more}" /* environment variable, or default */

int
main(int argc, char *argv[])
{
        char    line[MAXLINE];
        FILE    *fpin, *fpout;

        if (argc != 2)
                err_quit("usage: a.out <pathname>");
        if ((fpin = fopen(argv[1], "r")) == NULL)
                err_sys("can't open %s", argv[1]);

        if ((fpout = popen(PAGER, "w")) == NULL)
                err_sys("popen error");

        /* copy argv[1] to pager */
        while (fgets(line, MAXLINE, fpin) != NULL) {
                if (fputs(line, fpout) == EOF)
                        err_sys("fputs error to pipe");
        }
        if (ferror(fpin))
                err_sys("fgets error");
        if (pclose(fpout) == -1)
                err_sys("pclose error");

        exit(0);
}



 ivhb 回复于:2007-07-05 14:27:04

如果要提供一个函数给其他人用,ferror判断就有用了吧。
比如读取informix下unload出来的文件,如果行尾是表示连接下一行。
如果提供一个函数叫做

static FILE *fp;

static int
init_file_read(char *filenmame)
{
  fp = fopen(filename, "r");
}

int 
has_next_line(register char *s, char spec)
{
  char  c, tr = 0;

  if (s[0] == 0x00)
    return (0);

  while (c = *s ++) {
    if (tr == 1
         && s[0] == 0x00
         && c == 'n')
      return (1);

    if (c != spec)
      tr = 0;
    else
      tr = 1 - tr;
  }

  return (0);
}

int
get_consecutive_lines(char *s, int len)
{
  static char spec = '';
  int lines = 0;
  int cur_line_len = 0;

  if (fp == NULL)
    return (-1);

  /* s remains unchanged */
  if (len < 2)
    return (0);

  do {
    s += cur_line_len;
    len -= cur_line_len;

    if (len < 2)
      break;

    if (fgets(s, len, fp) == NULL)
      break;

    cur_line_len = strlen(s);

    if (s[0] != 0x00
         && s[cur_line_len-1] == 'n')
      lines ++;
  } while (has_next_line(s, spec));

  if (ferror(fp))
    return (-1);

  return (lines);
}



这样可以约定返回<0就是出错,>= 0 就是实际读取的行数,表示成功。
调用你函数的人就可以根据返回来决定采取什么动作了。
.....
我们不讨论这么封装模块化的优劣,仅仅是个例子来说明一下而已。

[ 本帖最后由 ivhb 于 2007-7-5 14:30 编辑 ]


 思一克 回复于:2007-07-05 15:22:24

gets() and fgets() return s on success, and NULL on error or when  end
       of file occurs while no characters have been read.

fgets错误或EOF都返回NULL


 福瑞哈哥 回复于:2007-07-05 15:48:01

引用:原帖由 思一克 于 2007-7-5 15:22 发表 [url=http://bbs.chinaunix.net/redirect.php?goto=findpost&pid=7010802&ptid=956786]
gets() and fgets() return s on success, and NULL on error or when  end
       of file occurs while no characters have been read.

fgets错误或EOF都返回NULL 



是的,fgets在eof和出错时都会返回NULL,只是一般情况下可以忽略错误。当然,可以更严谨一些。


 封神 回复于:2007-07-09 09:45:33

汗,没想到已经讨论到这么高深的地步了,太复杂了,我还是只记住while(fgets(...))就好了


 思一克 回复于:2007-07-09 10:36:48

其实也没有什么高深的。就是你判断fgets是否真返回了内容就可以了。

还有,CLIB库已经非常成熟,一旦出莫名其妙的问题,先研究自己程序那里有问题了,而不是怀疑系统BUG。
如果细心看MAN PAGE,一切解释都有。

比如,putenv()的参数问题,我也莫名其妙过,但一看man page,就不再上当了。


引用:原帖由 封神 于 2007-7-9 09:45 发表 [url=http://bbs.chinaunix.net/redirect.php?goto=findpost&pid=7024443&ptid=956786]
汗,没想到已经讨论到这么高深的地步了,太复杂了,我还是只记住while(fgets(...))就好了 




 ivhb 回复于:2007-07-09 10:46:17

呵呵,手册页真是好东西,对于可能出现的错误都会提醒你,而不是藏着掖着。


 封神 回复于:2007-07-10 10:22:35

恩恩,收到了.谢谢大家的指点.

PS:因为E文比较差,看E文资料十分痛苦,哎,什么时候汉语成为世界通用语就好了(无聊的YY中).........




原文链接:http://bbs.chinaunix.net/viewthread.php?tid=956786
转载请注明作者名及原文出处


文章评论

共有 位网友发表了评论 查看完整内容