2015年1月9日 星期五

Linux下DIR,dirent,stat等结构体详解

  
  DIR结构体类似于FILE,是一个内部结构,以下几个函数用这个内部结构保存当前正在被读取的目录的有关信息(摘自《UNIX环境高级编程(第二版)》)。函数 DIR *opendir(const char *pathname),即打开文件目录,返回的就是指向DIR结构体的指针,而该指针由以下几个函数使用:

首先说说DIR这一结构体,以下为DIR结构体的定义:
  1. struct __dirstream
  2.    {
  3.     void *__fd;
  4.     char *__data;
  5.     int __entry_data;
  6.     char *__ptr;
  7.     int __entry_ptr;
  8.     size_t __allocation;
  9.     size_t __size;
  10.     __libc_lock_define (, __lock)
  11.    };
  12. typedef struct __dirstream DIR;

DIR结构体类似于FILE,是一个内部结构,以下几个函数用这个内部结构保存当前正在被读取的目录的有关信息(摘自《UNIX环境高级编程(第二版)》)。函数 DIR *opendir(const char *pathname),即打开文件目录,返回的就是指向DIR结构体的指针,而该指针由以下几个函数使用:

  1. struct dirent *readdir(DIR *dp);
  2. void rewinddir(DIR *dp);
  3. int closedir(DIR *dp);
  4. long telldir(DIR *dp);
  5. void seekdir(DIR *dp,long loc);

关于DIR结构,我们知道这么多就可以了,没必要去再去研究他的结构成员。
接着是dirent结构体,首先我们要弄清楚目录文件(directory file)的概念:这种文件包含了其他文件的名字以及指向与这些文件有关的信息的指针(摘自《UNIX环境高级编程(第二版)》)。从定义能够看出,dirent不仅仅指向目录,还指向目录中的具体文件,readdir函数同样也读取目录下的文件,这就是证据。以下为dirent结构体的定义:

  1. struct dirent
  2. {
  3.   long d_ino; /* inode number 索引节点号 */
  4.     off_t d_off; /* offset to this dirent 在目录文件中的偏移 */
  5.     unsigned short d_reclen; /* length of this d_name 文件名长 */
  6.     unsigned char d_type; /* the type of d_name 文件类型 */
  7.     char d_name [NAME_MAX+1]; /* file name (null-terminated) 文件名,最长255字符 */
  8. }

从上述定义也能够看出来,dirent结构体存储的关于文件的信息很少,所以dirent同样也是起着一个索引的作用,如果想获得类似ls -l那种效果的文件信息,必须要靠stat函数了。
通过readdir函数读取到的文件名存储在结构体dirent的d_name成员中,而函数
int stat(const char *file_name, struct stat *buf);
的作用就是获取文件名为d_name的文件的详细信息,存储在stat结构体中。以下为stat结构体的定义:

  1. struct stat {
  2.         mode_t     st_mode;       //文件访问权限
  3.         ino_t      st_ino;       //索引节点号
  4.         dev_t      st_dev;        //文件使用的设备号
  5.         dev_t      st_rdev;       //设备文件的设备号
  6.         nlink_t    st_nlink;      //文件的硬连接数
  7.         uid_t      st_uid;        //所有者用户识别号
  8.         gid_t      st_gid;        //组识别号
  9.         off_t      st_size;       //以字节为单位的文件容量
  10.         time_t     st_atime;      //最后一次访问该文件的时间
  11.         time_t     st_mtime;      //最后一次修改该文件的时间
  12.         time_t     st_ctime;      //最后一次改变该文件状态的时间
  13.         blksize_t st_blksize;    //包含该文件的磁盘块的大小
  14.         blkcnt_t   st_blocks;     //该文件所占的磁盘块
  15.       };

这个记录的信息就很详细了吧,呵呵。
最后,总结一下,想要获取某目录下(比如a目下)b文件的详细信息,我们应该怎样做?
首先,我们使用opendir函数打开目录a,返回指向目录a的DIR结构体c。
接着,我们调用readdir(c)函数读取目录a下所有文件(包括目录),返回指向目录a下所有文件的dirent结构体d。
然后,我们遍历d,调用stat(d->name,stat *e)来获取每个文件的详细信息,存储在stat结构体e中。
总体就是这样一种逐步细化的过程,在这一过程中,三种结构体扮演着不同的角色。

以下範例為開啟某目錄後,搜尋目錄下所有".c"副檔名,此處是用指針偏移的方式來取得附檔名,另外也可以用函數fnmatch來實現。

#include<stdio.h>
#include<string.h>
#include<unistd.h>
#include<dirent.h>
#include<stdlib.h>

int main (){
    DIR *d;
    struct dirent *de;
    char * buf;

    d=opendir("/root/pratice/day5");
    if(d==NULL){printf("%m\n"),exit(-1);}

    while((de=readdir(d)))
    {
    int length=strlen(de->d_name);
    int ext=length-2;
    buf=strstr(de->d_name+ext,".c");
    if (buf){printf("%s\n",de->d_name);}
    }
    return 0;
}

2015年1月6日 星期二

curses.h No such file or directory problem

Fix this error on RHEL / Fedora / CentOS Linux

Type the following yum command at a shell prompt as root user:
# yum install ncurses-devel ncurses
Sample output:
Loading "downloadonly" plugin
Loading "rhnplugin" plugin
Loading "security" plugin
rhel-x86_64-server-vt-5   100% |=========================| 1.2 kB    00:00
rhel-x86_64-server-5      100% |=========================| 1.2 kB    00:00
Setting up Install Process
Parsing package install arguments
Package ncurses - 5.5-24.20060715.x86_64 is already installed.
Package ncurses - 5.5-24.20060715.i386 is already installed.
Resolving Dependencies
--> Running transaction check
---> Package ncurses-devel.x86_64 0:5.5-24.20060715 set to be updated
---> Package ncurses-devel.i386 0:5.5-24.20060715 set to be updated
--> Finished Dependency Resolution
Dependencies Resolved
=============================================================================
 Package                 Arch       Version          Repository        Size
=============================================================================
Installing:
 ncurses-devel           x86_64     5.5-24.20060715  rhel-x86_64-server-5  1.7 M
 ncurses-devel           i386       5.5-24.20060715  rhel-x86_64-server-5  1.6 M
Transaction Summary
=============================================================================
Install      2 Package(s)
Update       0 Package(s)
Remove       0 Package(s)
Total download size: 3.3 M
Is this ok [y/N]: y
Downloading Packages:
(1/2): ncurses-devel-5.5- 100% |=========================| 1.6 MB    00:01
(2/2): ncurses-devel-5.5- 100% |=========================| 1.7 MB    00:01
Running rpm_check_debug
Running Transaction Test
Finished Transaction Test
Transaction Test Succeeded
Running Transaction
  Installing: ncurses-devel                ######################### [1/2]
  Installing: ncurses-devel                ######################### [2/2]
Installed: ncurses-devel.x86_64 0:5.5-24.20060715 ncurses-devel.i386 0:5.5-24.20060715
Complete!

Fix this error on Debian / Ubuntu Linux

Open terminal and type the following apt-get command to install ncurses:
$ sudo apt-get install libncurses5-dev libncursesw5-dev
Now you should able to compile any program under Linux using ncurses.

2015年1月4日 星期日

void * 和 void 在函数返回值中的区别

一个很容易糊涂的问题.

在函数的返回值中, void 是没有任何返回值, 而 void * 是返回任意类型的值的指针.
还是看代码吧:
#include <stdlib.h>
#include <stdio.h>

void voidc(int a); 
void* voidcp(int *a);
int main(){
    int      a=10;
    int     *ap;

    voidc(a);
    ap = voidcp(&a);
    printf("%d\n",*ap);

    return 0;
}
void voidc(int a){ 
    printf("%d\n",a);
    return;    // 没有返回值
}
void* voidcp(int *a){
    printf("%d\n", *a);
    return a;  // 返回 int *
}

2015年1月3日 星期六

C 字符串分割

函数名: strtok
功  能: 查找由在第二个串中指定的分界符分隔开的单词
用  法: char *strtok(char *str1, char *str2);
程序例:

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

int main(void)
{
   char input[16] = "abc,d";
   char *p;

   /* strtok places a NULL terminator
   in front of the token, if found */
   p = strtok(input, ",");
   if (p)   printf("%s\n", p);

   /* A second call to strtok using a NULL
   as the first parameter returns a pointer
   to the character following the token  */
   p = strtok(NULL, ",");
   if (p)   printf("%s\n", p);
   return 0;
}
這個程式的輸出如下所示:
下列是程式的上述在執行期間中語彙基元的指標周圍的記憶體區域的範例表示。請注意取代以 NULL 字元分隔符號的語彙基元位於每個時間:
   -------------------------------------------------------------
   |a |  |s |t |r |i |n |g |, |o |f |  |, |, |t |o |k |e |n |s |
   -------------------------------------------------------------
   This is the original string before the first call to strtok().


   -------------------------------------------------------------
   |a |\0|s |t |r |i |n |g |, |o |f |  |, |, |t |o |k |e |n |s |
   -------------------------------------------------------------
    ^----- Token will point here on the first call.

   -------------------------------------------------------------
   |a |\0|s |t |r |i |n |g |\0|o |f |  |, |, |t |o |k |e |n |s |
   -------------------------------------------------------------
          ^------ Token will point here on the second call.

   -------------------------------------------------------------
   |a |\0|s |t |r |i |n |g |\0|o |f |\0|, |, |t |o |k |e |n |s |
   -------------------------------------------------------------
                               ^----- Token will point here on
                                      the third call.

                                (and so on)
    

C 語言新手十誡

C 語言新手十誡(The Ten Commandments for Newbie C Programmers)

                                                       by Khoguan Phuann
請注意:

(1) 本篇旨在提醒新手,避免初學常犯的錯誤(其實老手也常犯:-Q)。
    但不能取代完整的學習,請自己好好研讀一兩本 C 語言的好書,
    並多多實作練習。

(2) 強烈建議新手先看過此文再發問,你的問題極可能此文已經提出並
    解答了。

(3) 以下所舉的錯誤例子如果在你的電腦上印出和正確例子相同的結果,
    那只是不足為恃的一時僥倖。

(4) 不守十誡者,輕則執行結果的輸出數據錯誤,或是程式當掉,重則
    引爆核彈、毀滅地球(如果你的 C 程式是用來控制核彈發射器的話)。


一、你不可以使用尚未給予適當初值的變數。

    錯誤例子:
    int accumulate(int max)    /* 從 1 累加到 max,傳回結果 */
    {
        int sum;    /* 未給予初值的區域變數,其內容值是垃圾 */
        int num;
        for (num = 1; num <= max; num++) {
            sum += num;
        }
        return sum;
    }

    正確例子:
    int accumulate(int max)
    {
        int sum = 0;    /* 正確的賦予適當的初值 */
        int num;
        for (num = 1; num <= max; num++) {
            sum += num;
        }
        return sum;
    }

二、你不可以存取超過陣列既定範圍的空間。

    錯誤例子:
    int str[5];
    int i;
    for (i = 0; i <= 5; i++) str[i] = i;

    正確例子:
    int str[5];
    int i;
    for (i = 0; i < 5; i++) str[i] = i;

    說明:宣告陣列時,所給的陣列元素個數值如果是 N, 那麼我們在後面
    透過 [索引值] 存取其元素時,所能使用的索引值範圍是從 0 到 N-1,
    也就是 C 和 C++ 的陣列元素是從第 0 個開始算起,最後一個元素的
    索引值是 N-1, 不是 N。

    C/C++ 為了執行效率,並不會自動檢查陣列索引值是否超過陣列邊界,
    我們要自己寫程式來確保不會越界。一旦越界,將導致無法預期的後果。

三、你不可以提取(dereference)不知指向何方的指標(包含 null 指標)。

    錯誤例子:
    char *pc1;      /* 未給予初值,不知指向何方 */
    char *pc2 = 0;  /* pc2 起始化為 null pointer */
    *pc1 = 'a';     /* 將 'a' 寫到不知何方,錯誤 */
    *pc2 = 'b';     /* 將 'b' 寫到「位址0」,錯誤 */

    正確例子:
    char c;          /* c 的內容尚未起始化 */
    char *pc1 = &c;  /* pc1 指向字元變數 c */

    /* 動態分配 10 個 char(其值未定),並將第一個char的位址賦值給 pc2 */
    char *pc2 = (char *)malloc(10);
    *pc1 = 'a';      /* c 的內容變為 'a' */
    pc2[0] = 'b';    /* 動態配置來的第 0 個字元,內容變為 'b'
    /* 最後記得 free() 掉 malloc() 所分配的空間 */
    free(pc2);

    說明:指標變數必需先指向某個明確的東西(object),才能進行操作。

四、你不可以將字串常數賦值(assign)給 char* 變數,然後透過該變數
    改寫字串的內容(只能讀不能寫)。

    錯誤例子:
    char* pc = "john";
    *pc = 'J';
    printf("Hello, %s\n", pc);

    正確例子:
    char pc[] = "john";
    *pc = 'J';  /* 或  pc[0] = 'J';  */
    printf("Hello, %s\n", pc);

    說明:字串常數的內容是唯讀的。上面的錯誤例子,是將其內容所在的位址賦
    值給字元指標 pc, 我們透過指標只可以去讀該字串常數的內容,而不應該做
    寫入的動作。而正確例子,則是另外宣告一個獨立的字元陣列,它的大小我們
    未明文指定([]),編譯器會自動將其設為剛好可以容納後面的字串常數起始
    值的大小,包括字串後面隱含的 '\0' 字元,並將字串常數的內容複製到字元
    陣列中,因此可以自由的對該字元陣列的內容進行讀和寫。

    錯誤例子(2):
    char *s1 = "Hello, ";
    char *s2 = "world!";
    /* strcat() 不會另行配置空間,只會將資料附加到 s1 所指唯讀字串的後面,
       造成寫入到程式無權碰觸的記憶體空間 */
    char *s3 = strcat(s1, s2);

    正確例子(2):
    /* s1 宣告成陣列,並保留足夠空間存放後續要附加的內容 */
    char s1[20] = "Hello, ";
    char *s2 = "world!";
    /* 因為 strcat() 的返回值等於第一個參數值,所以 s3 就不需要了 */
    strcat(s1, s2);

五、你不可以對尚未分配所指空間的 char* 變數,進行(字串)陣列的相關操作。
    其他型別的指標亦然。

    錯誤例子:
    char *name;   /* name 尚未指向有效的空間 */
    printf("Your name, please: ");
    gets(name);
    printf("Hello, %s\n", name);

    正確例子(1):
    /* 如果編譯期就能決定字串的最大空間,那就不要宣告成 char* 改用 char[] */
    char name[21]; /* 字串最長 20 個字元,另加一個 '\0' */
    printf("Your name, please: ");
    gets(name);
    printf("Hello, %s\n", name);

    正確例子(2):
    /* 若是在執行時期才能決定字串的最大空間,則需利用 malloc() 函式來動態
       分配空間 */

    size_t length;
    char *name;
    printf("請輸入字串的最大長度(含null字元): ");
    scanf("%u", &length);
    name = (char *)malloc(length);
    printf("Your name, please: ");
    scanf("%s", name);
    printf("Hello, %s\n", name);
    /* 最後記得 free() 掉 malloc() 所分配的空間 */
    free(name);

    注意:上例用 gets() 或 scanf() 來讀入字串,是不安全的。 因為這些函式
    不會幫我們檢查使用者所輸入的字串長度是否超過我們所分配的 buffer 空間,
    很可能會發生 buffer overflow。比較安全的做法是用 fgets() 來取代。如:

    char *p;
    char name[21];
    printf("Your name, please: ");
    fgets(name, sizeof(name), stdin);
    /* fgets()會連行末的'\n'也讀進字串中,所以要找出存入'\n'的位置,填入 '\0'
    if ((p = strchr(name, '\n')) != NULL)
        *p = '\0';
    printf("Hello, %s\n", name);

六、你不可以在函式中回傳一個指向區域性自動變數的指標。否則,會得到垃圾值。
    [感謝 gocpp 網友提供程式例子]

    錯誤例子:
    char *getstr(char *name)
    {
        char buf[30] = "hello, "; /*將字串常數"hello, "的內容複製到buf陣列*/
        strcat(buf, name);
        return buf;
    }

    說明:區域性自動變數,將會在離開該區域時(本例中就是從getstr函式返回時)
    被消滅,因此呼叫端得到的指標所指的字串內容就失效了。【不過,倒是可以從
    函式中直接傳回字串常數,賦值給呼叫端的一個 const char * 變數,它既是唯
    讀的(參見第四誡),同時也具有恒常的儲存期(static storage duration),其
    內容將一直有效。】

    正確例子:
    void getstr(char buf[], int buflen, char const *name)
    {
        char const s[] = "hello, ";
        assert(strlen(s) + strlen(name) < buflen);

        strcpy(buf, s);
        strcat(buf, name);
    }

    [針對字串操作,C++提供了更方便安全的 string class, 能用就盡量用]
    #include <string>
    using std::string;

    string getstr(string const &name)
    {
        return string("hello, ") += name;
    }

七、你不可以只做 malloc(), 而不做相應的 free(). 否則會造成記憶體漏失。

    但若不是用 malloc() 所得到的記憶體,則不可以 free()。已經 free()了
    所指記憶體的指標,在它指向另一塊有效的動態分配得來的空間之前,不可
    以再被 free(),也不可以提取(dereference)這個指標。

    [C++] 你不可以只做 new, 而不做相應的 delete.

八、你不可以在數值運算、賦值或比較中隨意混用不同型別的數值,而不謹慎考
    慮數值型別轉換可能帶來的「意外驚喜」(錯愕)。必須隨時注意數值運算
    的結果,其範圍是否會超出變數的型別。

    錯誤例子(1):
    unsigned int sum = 2000000000 + 2000000000; /* 20 億 */
    double f = 10 / 3;

    正確例子(1):
    /* 全部都用 unsigned int, 注意數字後面的 u, 大寫 U 也成 */
    unsigned int sum = 2000000000u + 2000000000u;

    /* 或是用顯式的轉型 */
    unsigned int sum = (unsigned int)2000000000 + 2000000000;

    double f = 10.0 / 3.0;

    說明:在目前最普遍的32位元PC作業平台上,整數常數2000000000的型別為
    signed int(簡寫為 int),相加後,其結果仍為 int, 但是 signed int
    放不下 4000000000, 造成算術溢位(arithmetic overflow),很可能無法
    將正確的值指派給 unsigned int sum,縱使 unsigned int 放得下4000000000
    的數值。注意:寫成

    unsigned int sum = (unsigned int)(2000000000 + 2000000000);

    也是不對的。

    例子(2):(感謝 sekya 網友提供)
    unsigned char a = 0x80;
    char b = 0x80;           /* implementation-defined result */
    if( a == 0x80 ) {        /* 恒真 */
        printf( "a ok\n" );
    if( b == 0x80 ) {        /* 不一定恒真 */
        printf( "b ok\n" );
    }

    說明:在將 char 型別定義為範圍從 -128 至 +127 的系統上,int 0x80
    (其值等於 +128)要轉成 char 會放不下,會產生編譯器自行定義的值。
    這樣的程式就不具可移植性了。

九、你不可以在一個運算式(expression)中,對一個基本型態的變數修改其值
    超過一次以上。否則,將導致未定義的行為(undefined behavior)。

    錯誤例子:
    int i = 7;
    int j = ++i + i++;

    正確例子:
    int i = 7;
    int j = ++i;
    j += i++;

    你也不可以在一個運算式(expression)中,對一個基本型態的變數修改其值,
    而且還在同一個式子的其他地方為了其他目的而存取該變數的值。(其他目的,
    是指不是為了計算這個變數的新值的目的)。否則,將導致未定義的行為。

    錯誤例子:
    int arr[5];
    int i = 0;
    arr[i] = i++;

    正確例子:
    int arr[5];
    int i = 0;
    arr[i] = i;
    i++;

    [C++程式]
    錯誤例子:
    int i = 10;
    cout << i << "==" << i++;

    正確例子:
    int i = 10;
    cout << i << "==";
    cout << i++;

十、你不可以在macro的定義中,不為它的參數個別加上括號。

    錯誤例子:
    #include <stdio.h>
    #define SQUARE(x)    (x * x)
    int main()
    {
        printf("%d\n", SQUARE(10-5));
        return 0;
    }

    正確例子:
    #include <stdio.h>
    #define SQUARE(x)    ((x) * (x))
    int main()
    {
        printf("%d\n", SQUARE(10-5));
        return 0;
    }

    說明:如果是用 C++, 請多多利用 inline function 來取代上述的 macro,
    以免除 macro 定義的種種危險性。如:

    inline int square(int x) { return x * x; }

    macro 定義出的「偽函式」至少缺乏下列數項函式本有的能力:

    (1) 無法進行參數型別的檢查。
    (2) 無法遞迴呼叫。
    (3) 無法用 & 加在 macro name 之前,取得函式位址。
    (4) 呼叫時往往不能使用具有 side effect 的引數。例如:

    錯誤例子:(感謝 yaca 網友提供)
    #define MACRO(x)     (((x) * (x)) - ((x) * (x)))
    int main()
    {
        int x = 3;
        printf("%d\n", MACRO(++x));
        return 0;
    }

    MACRO(++x) 展開來後變成 (((++x) * (++x)) - ((++x) * (++x)))
    違反了第九誡。在 gcc 4.3.3 下的結果是 -24, 在 vc++ 下是 0.

2015年1月2日 星期五

在Linux下如何使用GCC编译程序、简单生成 静态库及动态库。

本文适用于Linux下开发初学者。本文初步讲解在Linux下如何使用GCC编译程序、简单生成静态库及动态库。

一、关于安装。一般系统默认是安装好编译器的,并且网络上有大量资料介绍不同发行版本下的安装问题,本文不再描述。

二、C编程中的文件后缀名介绍
    .a 静态库(打包文件)
    .c 未经过预处理的C源码
    .h C头文件   
    .i 经过预处理的C源码
    .o 编译之后产生的目标文件
    .s 生成的汇编语言代码
    .so 动态库(动态链接库)
    解释:*.a是我们在编译过后用ar打包生成的静态库;*.c一般使我们自己编辑的代码,使我们劳动的结晶;*.h一般是 我们手工生成的接口文件,如果愿意,也可在*.c完成后用GCC的选项-aux-info帮我们生成;*.i是经过预处理后的源码,是由GCC在选项-E编译下自动生成 的文件;*.o是编 译后产生的目标文件;*.s是GCC在选项-S编译下生成的汇编语言代码,对于性能要求很高的程序可以先生成汇编语言文件并对汇编做优化,然后用优 化后的汇编生成目标文件并链接;*.so是动态库,通过GCC的-fpic -shared选项生成。

三、hello.c的编译过程

    本小节的演示都针对文件 hello.c 进行
  1. /*
  2.  * hello.c
  3.  */

  4.   #include <stdio.h>
  5.   int  main()
  6.   {
  7.     printf("hello, world!/n");
  8.     return 0;
  9.   }
1. 直接生成可执行程序
  1. $ gcc -o hello hello.c
  2. $ ./hello
  3. hello, world!

  4. 如 下编译方式 结果相同:
  5. $ gcc hello.c -o hello
  6. $ ./hello
  7. hello, world!

  8. 如 下编译方式 有别于以上编译方 案(具体查找ELF和a.out文件格式差别的网络资料,对于此处结果是无任何区别的):
  9. $ gcc hello.c 
  10. $ ./a.out 
  11. hello, world!

2. 生成预处理后的文件 hello.i
  1. $ gcc -E hello.c -o hello.i
  2. $ ls
  3. a.out  hello  hello.c  hello.i
  4. hello.i 就 是新生成的文件

  5. 如下语句结果相同:
  6. $ gcc -E -o hello.i hello.c 

  7. 如 果不设定输出文件,则打印到标准终端,此时我们可以用 less 查看:
  8. $ gcc -E hello.c | less
  9. # 1 "hello.c"
  10. # 1 "<built-in>"
  11. # 1 "<command line>"
  12. # 1 "hello.c"
  13. # 1 "/usr/include/stdio.h" 1 3 4
  14. # 28 "/usr/include/stdio.h" 3 4
  15. # 1 "/usr/include/features.h" 1 3 4
  16. # 329 "/usr/include/features.h" 3 4
  17. ..............................

  18. 或 者执行:
  19. $ gcc -E hello.c -o hello.i
  20. $ vi hello.i
  21.   1 # 1 "hello.c"
  22.   2 # 1 "<built-in>"
  23.   3 # 1 "<command line>"
  24.   4 # 1 "hello.c"
  25.   5 # 1 "/usr/include/stdio.h" 1 3 4
  26.   6 # 28 "/usr/include/stdio.h" 3 4
  27.   7 # 1 "/usr/include/features.h" 1 3 4
  28.   8 # 329 "/usr/include/features.h" 3 4

  29. .......... < 中间部分略> ..................

  30. 929 # 844 "/usr/include/stdio.h" 3 4
  31. 930 
  32. 931 # 2 "hello.c" 2
  33. 932 
  34. 933 int main()
  35. 934 {
  36. 935         printf("hello, world!/n");
  37. 936 
  38. 937         return 0;
  39. 938 }

  40. 可 见,将近1000行的代码,我们的只占了最末8行。

3.生成汇编语言文件 hello.s
  1. $ gcc -S hello.c -o hello.s
  2. $ ls
  3. a.out  hello  hello.c  hello.i  hello.s
  4. hello.s 就是新生成的文件

  5. 如下语句结果相同:
  6. $ gcc -S -o hello.s hello.c

  7. 如 下语句结果相同:
  8. $ gcc -S hello.c

  9. 也 可以采用前一步骤产生的中间文件生成汇编文件:
  10. $ gcc -S hello.i -o hello.s
  11. $ gcc -S -o hello.s hello.i
  12. $ gcc -S hello.i


  13. 生 成的汇编部分代码如下:
  14. $ vi hello.s 
  15.   1         .file   "hello.c"
  16.   2         .section        .rodata
  17.   3 .LC0:
  18.   4         .string "hello, world!"
  19.   5         .text
  20.   6 .globl main
  21.   7         .type   main, @function
  22.   8 main:
  23.   9         leal    4(%esp), %ecx
  24.  10         andl    $-16, %esp
  25.  11         pushl   -4(%ecx)
  26.  12         pushl   %ebp
  27. // 注释:如果你熟悉,就可以对部分汇编优化以达到更好效果。
4.生成目标文件 hello.o
  1. $ gcc -c hello.c -o hello.o
  2. $ ls
  3. a.out  hello  hello.c  hello.i  hello.o  hello.s
  4. hello.o 就是新生成的目标文件:

  5. 如下语句结果相同:
  6. $ gcc -c -o hello.o hello.c 

  7. 如 下语句结果相同:
  8. $ gcc -c hello.c

  9. 也 可以采用前面步骤产生的中间文件hello.i或hello.s来生成目标文件:
  10. $ gcc -c hello.i
  11. $ gcc -c hello.s

  12. 我 们可以用 objdump 查看 hello.o 的二进制码:
  13. $ objdump -s hello.o

  14. hello.o:     file format elf32-i386

  15. Contents of section .text:
  16.  0000 8d4c2404 83e4f0ff 71fc5589 e55183ec  .L$.....q.U..Q..
  17.  0010 04c70424 00000000 e8fcffff ffb80000  ...$............
  18.  0020 000083c4 04595d8d 61fcc3             .....Y].a..     
  19. Contents of section .rodata:
  20.  0000 68656c6c 6f2c2077 6f726c64 2100      hello, world!.  
  21. Contents of section .comment:
  22.  0000 00474343 3a202847 4e552920 342e312e  .GCC: (GNU) 4.1.
  23.  0010 31203230 30373031 30352028 52656420  1 20070105 (Red 
  24.  0020 48617420 342e312e 312d3532 2900      Hat 4.1.1-52).  
5. 采用中间级文件生成可执行程序
  1. $ gcc -o hello hello.i
  2. $ ./hello
  3. hello, world!

  4. $ gcc -o hello hello.s
  5. $ ./hello
  6. hello, world!

  7. $ gcc -o hello hello.o
  8. $ ./hello
  9. hello, world!
四、 静态库的生成
    linux下静态库的生成比较方便。在生成目标文件后用 ar 打包即可。在中大型项目中一个模块一般会做成一个静态库,以方便管理、提高编译、链接效率。
    本小节的展示针对 main.c、func1.c、func2.c三个文件
  1. /*
  2.  * main.c
  3.  */
  4. #include <stdio.h>

  5. extern int func1();
  6. extern int func2();

  7. int main()
  8. {
  9.         int i;

  10.         i = func1();
  11.         printf("func1 return = %d/n",i);

  12.         i = func2();
  13.         printf("func2 return = %d/n",i);

  14.         return 0;
  15. }

-----------------------------------------------------
  1. /*
  2.  * func1.c
  3.  */
  4. int func1()
  5. {
  6.         return 100;
  7. }
-----------------------------------------------------
  1. /*
  2.  * func2.c
  3.  */
  4. int func2()
  5. {
  6.         return 200;
  7. }
一 下是编译指 令:
  1. $ gcc -c func1.c
  2. $ gcc -c func2.c
  3. $ ls
  4. func1.c  func1.o  func2.c  func2.o  main.c

  5. func1.o 和 func2.o 是 我们生成的目标文件。打包指令如下:
  6. $ ar -r libfunc.a func1.o func2.o

  7. 我 们查看 libfunc.a 中的文件:
  8. $ ar -t libfunc.a 
  9. func1.o
  10. func2.o

  11. 现 在用静态库和 main.c 共同生成目标程序:
  12. $ gcc -o main main.c libfunc.a
  13. $ ./main 
  14. func1 return = 100
  15. func2 return = 200

  16. 和 我们的预期相符合。下面我们进入动态库。


五、动态库的生成
    linux下动态库的生成通过GCC选项实现。案例程序和静态库中的相同。一下是操作指令:
  1. 首 先我们生成目标文件,但是需要加编译器选项 -fpic 和链接器选项 -shared
  2. $ gcc -fpic -c func1.c
  3. $ gcc -fpic -c func2.c
  4. $ gcc -shared -o libfunc.so func1.o func2.o
  5. $ ls
  6. func1.c  func1.o  func2.c  func2.o  libfunc.so  main.c

  7. libfunc.so 就是我们生成的目标动态库。我们用动态库和 main.c 生成目标程序:
  8. $ gcc -o main main.c -L. -lfunc

  9. 注 意,我们用 -L. -lfunc 作为编译选项。-L. 表从当前目录查找需要的动态库,-lfunc 是动态库的调用规则。Linux系统下的动态库命名方 式是 lib*.so,而在链接时表示位 -l* , *是自己起的库名。下面我们运行它:

  10. $ ./main 
  11. ./main: error while loading shared libraries: libfunc.so: cannot open shared object file: No such file or directory

  12. 提 示一个错误, 指示无法找到动态库。在linux下最方便的解决方案是拷贝libfunc.so到绝对目录 /lib 下。但是只有超级用户才有这个权限。另外一个方案 是更改环境变量 LD_LIBRARY_PATH。如下:
  13. $ $ export LD_LIBRARY_PATH=`pwd`
  14. $ ./main 
  15. func1 return = 100
  16. func2 return = 200

  17. 运 行成功。现在我们更改动态库的函数而不重新链接。如下:
  18. 更改 func1.c 为:
  19. int func1()
  20. {
  21.         return 101;
  22. }
  23. 更 改 func2.c 为:
  24. int func2()
  25. {
  26.         return 202;
  27. }
  28. 重 新生成库:
  29. $ gcc -fpic -shared func1.c func2.c -o libfunc.so
  30. $ ./main 
  31. func1 return = 101
  32. func2 return = 202

  33. 可 以看出,动态库已经更新了。
六、结束语
    本文简单介绍了linux下如何使用gcc进行编译程序、以及简 单的静态、动态库的生成。静态库提供了一种打包管理方案,而动态库使程序局部更新成为了可能,更重要的是,当有多份实例存在时,动态库可减小内存的消耗 (只占用一份代码空间)。

2015年1月1日 星期四

由鍵盤輸入整數N,判斷1~N整數有幾個質數

  1 #include <stdio.h>
  2 #include <unistd.h>
  3
  4
  5 int main (){
  6
  7     int i;
  8     int j;
  9     int k;
 10     int count=0;
 11
 12     printf("Inpit a number : ");
 13     scanf("%d",&k);
 14
 15     for (j=2;j<=k;j++)
 16     {
 17         int isPrime=0;
 18         for (i=2; (i*i)<=j;i++)
 19         {
 20             if(j%i==0)
 21             {
 22             isPrime++;
 23             }
 24
 25         }
 26         if(isPrime==0)
 27         {
 28         count++;
 29         printf("%d ",j);
 30         }
 31
 32     }
 33         printf("\n");
 34         printf("Total %d prime numbers\n",count);
 35 return 0;
 36 }
 37



輸出結果:
Inpit a number : 1000
2 3 5 7 11 13 17 19 23 29 31 37 41 43 47 53 59 61 67 71 73 79 83 89 97 101 103 107 109 113 127 131 137 139 149 151 157 163 167 173 179 181 191 193 197 199 211 223 227 229 233 239 241 251 257 263 269 271 277 281 283 293 307 311 313 317 331 337 347 349 353 359 367 373 379 383 389 397 401 409 419 421 431 433 439 443 449 457 461 463 467 479 487 491 499 503 509 521 523 541 547 557 563 569 571 577 587 593 599 601 607 613 617 619 631 641 643 647 653 659 661 673 677 683 691 701 709 719 727 733 739 743 751 757 761 769 773 787 797 809 811 821 823 827 829 839 853 857 859 863 877 881 883 887 907 911 919 929 937 941 947 953 967 971 977 983 991 997
Total 168 prime numbers