顯示具有 about C 標籤的文章。 顯示所有文章
顯示具有 about C 標籤的文章。 顯示所有文章

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月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

2014年12月18日 星期四

C语言 sprintf与sscanf函数

1、前言
    我们经常涉及到数字与字符串之间的转换,例如将32位无符号整数的ip地址转换为点分十进制的ip地址字符串,或者反过来。从给定的字符串中提取相关内容,例如给定一个地址:http://www.bokeyuan.cn:2345,我们要从地址中提出协议,主机地址和端口号。之前对字符串和数字之间的关系不是很熟悉,工作中经常涉及到这个,如是好好总结一下。C语言提供了一些列的格式化输入输出函数,最基本的是面向控制台标准输出和输入的printf和scanf,其实还有面向字符串的sprint和sscanf,面向文件的流的fprintf和fscanf。今天着重总结一下sprintf和sscanf系列函数,这两个函数类似于scanf和printf ,不同点是从字符串*buffer用于输入输出。
2、sprintf函数
  sprintf函数原型为 int sprintf(char *str, const char *format, ...)作用是格式化字符串,具体功能如下所示:
(1)将数字变量转换为字符串。
(2)得到整型变量的16进制和8进制字符串。
(3)连接多个字符串。
举例如下所示:
复制代码
 1     char str[256] = { 0 };
 2     int data = 1024;
 3     //将data转换为字符串
 4     sprintf(str,"%d",data);
 5     //获取data的十六进制
 6     sprintf(str,"0x%X",data);
 7     //获取data的八进制
 8     sprintf(str,"0%o",data);
 9     const char *s1 = "Hello";
10     const char *s2 = "World";
11     //连接字符串s1和s2
12     sprintf(str,"%s %s",s1,s2);
复制代码
3、sscanf函数
    sscanf函数原型为int sscanf(const char *str, const char *format, ...)将参数str的字符串根据参数format字符串来转换并格式化数据,转换后的结果存于对应的参数内。具体功能如下:
(1)根据格式从字符串中提取数据。如从字符串中取出整数、浮点数和字符串等。
(2)取指定长度的字符串
(3)取到指定字符为止的字符串
(4)取仅包含指定字符集的字符串
(5)取到指定字符集为止的字符串
sscanf可以支持格式字符%[]:
(1)-: 表示范围,如:%[1-9]表示只读取1-9这几个数字 %[a-z]表示只读取a-z小写字母,类似地 %[A-Z]只读取大写字母
(2)^: 表示不取,如:%[^1]表示读取除'1'以外的所有字符 %[^/]表示除/以外的所有字符
(3),: 范围可以用","相连接 如%[1-9,a-z]表示同时取1-9数字和a-z小写字母 
(4)原则:从第一个在指定范围内的数字开始读取,到第一个不在范围内的数字结束%s 可以看成%[] 的一个特例 %[^ ](注意^后面有一个空格!)
解析网址的例子如下所示:
复制代码
 1     const char *s = "http://www.baidu.com:1234";
 2     char protocol[32] = { 0 };
 3     char host[128] = { 0 };
 4     char port[8] = { 0 };
 5     sscanf(s,"%[^:]://%[^:]:%[1-9]",protocol,host,port);
 6 
 7     printf("protocol: %s\n",protocol);
 8     printf("host: %s\n",host);
 9     printf("port: %s\n",port);
10     
复制代码
4、snprintf函数
  snprintf函数是sprintf函数的更加安全版本,考虑到字符串的字节数,防止了字符串溢出。函数形式为:int snprintf(char *restrict buf, size_t n, const char * restrict  format, ...);。最多从源串中拷贝n-1个字符到目标串中,然后再在后面加一个0。所以如果目标串的大小为n 的话,将不会溢出。

2014年12月14日 星期日

C 語言 static 與 extern

在C中,談到可視範圍(scope)可分為許多層次,也可以談到很複雜,在這邊先談談「全域變數」(Global variable)、「區域變數」(Local variable)與「區塊變數」(Block variable)。

全域變數是指直接宣告在(主)函式之外的變數,這個變數在整個程式之中都「看」得它的存在,而可以呼叫使用,例如:

const double PI = 3.14159; 

doule area(double r) { 
    return r * r * PI; 
}

int main(void) { 
    // ..... 
    return 0; 

在這個例子中,PI這個變數可以被主函式main()與副函式area()來使用,通常全域變數是用來定義一些常數,初學者不應為了方便而將所有的變數都 設定為全域變數,否則將來一定會發生變數名稱管理上的問題,全域變數的生命週期始於程式開始之時,終止於程式結束之時。

區域變數是指宣告在函式之內的變數,或是宣告在參數列之前的變數,它的可視範圍只在宣告它的函式區塊之中,其它的函式不可以使用該變數,例如在上例的主函 式中,您不可以直接對area()函式中的變數r作出存取的動作,區域變數的生命週期開始於函式被呼叫之後,終止於函式執行完畢之時。

區塊變數是指宣告在某個陳述區塊之中的變數,例如while迴圈區塊中,或是for迴圈區塊,例如下面的變數i在迴圈結束之後,就會自動消失:

while(...) {
    int i = 0;
    // ....
}

當一可視範圍大的變數與可視範圍小的變數發生同名狀況時,可視範圍小的變數會暫時覆蓋可視範圍大的變數,稱之為「變數覆蓋」,例如: 

int num = 10;
int i;
for(i = 0; i < 100; i++)  {
    int num = 20;
    // ...
}
printf("%d", num);

這個程式最後顯示的 num 值仍是10,當執行迴圈時,迴圈內的 num 變數作用將覆蓋迴圈外的 num 變數;同樣的作用發生於全域變數與區域變數發生同名的時候。

再來介紹static變數,當變數有宣告時加上static限定時,一但變數生成,它就會一直存在記憶體之中,即使函式執行完畢,變數也不會消失,例如: 

#include <stdio.h>

void count(void);

int main(void) {
    int i;
    for(i = 0; i < 10; i++) {
        count();     
    }

    return 0;
}

void count(void) { 
    static int c = 1; 
    printf("%d\n", c); 
    c++; 
}

執行結果:
1
2
3
4
5
6
7
8
9
10


雖然變數c是在count()函式中宣告的,但是函式結束後,變數仍然存在,它會直到程式執行結束時才消失,雖然變數一直存在,但由於它是被宣告在函式之 中,所以函式之外仍無法存取static變數。

您可以宣告全域static變數,其在程式執行期間一直存在,但在一個原始程式文件中宣告全域static變數,還表示其可以存取的範圍僅限於該原始程式文件之中,您也可以將函式宣告為static:

static void some() {
    ...
}

一個static函式表示,其可以呼叫的範圍限於該原始碼文件之中,如果您有一些函式僅想在該原始程式文件之中使用,則可以宣告為static,這也可以避免與其他人寫的函式名稱衝突的問題。
extern可以聲明變數會在其它的位置被定義,這個位置可能是在同一份文件之中,或是在其它文件之中,例如: 

  • some.c
double someVar = 1000;
// 其它定義 ...

  • main.c
#include <stdio.h>

int main(void) {
    extern double someVar;
    
    printf("%f\n", someVar);
    
    return 0;
}

在main.c中實際上並沒有宣告someVar,extern指出someVar是在其它位置被定義,編譯器會試圖在其它位置或文件中找出 someVar的定義,結果在some.c中找到,因而會顯示結果為1000,要注意的是,extern聲明someVar在其它位置被定義,如果您 在使用extern時同時指定其值,則視為在該位置定義變數,結果就引發重覆定義錯誤,例如:

#include <stdio.h>

int main(void) {
    extern double someVar = 100; 
// error, `someVar' has both `extern' and initializer    
    ...
    return 0;
}

您必須先聲明extern找到變數,再重新指定其值,這麼使用才是正確的:

#include <stdio.h>

int main(void) {
    extern double someVar;
    
someVar = 100;
    ...
    return 0;
}

2014年12月11日 星期四

C 語言 字串處理

C 語言標準函數庫分類導覽 - 字串處理 string.h

標頭檔 string.h 宣告許多字串處理相關的函數,包括拷貝、相接、搜尋、測試相等、計算長度等。


以 str 起頭的函數作為處理字串之用,另有以 mem 起頭的函數,這些函數則可以進行記憶體區塊的操作。
 size_t 作為 sizeof 運算子的回傳型態,實際上可能為 unsigned int 或 unsigned long 。



以下函數可以拷貝字串
函數名稱功能函數原型
strcpy將字串 s2 拷貝到 s1char *strcpy(char *s1, const char *s2);
strncpy將字串 s2 最多 n 個字元拷貝到 s1char *strncpy(char *s1, const char *s2, size_t n);




以下函數可以將字串相接

函數名稱功能函數原型
strcat將字串 s2 接到 s1 的尾端char *strcat(char *s1, const char *s2);
strncat將字串 s2 最多 n 個字元接到 s1 的尾端char *strncat(char *s1, const char *s2, size_t);




以下函數測試兩個字串是否相等

函數名稱功能函數原型
strcmp比較 s1 與 s2 兩個字串是否相等int strcmp(const char *s1, const char *s2);
strncmp比較 s1 與 s2 兩個字串前 n 個字元是否相等int strncmp(const char *s1, const char *s2, size_t n);




以下函數作為字串的搜尋處理之用

函數名稱功能函數原型
strchr回傳在字串 s 中,字元 c 第一次出現位置的指標char *strchr(const char *s, int c);
strcspn計算經過幾個字元會在字串 s1 中遇到屬於 s2 中的字元size_t strcspn(const char *s1, const char *s2);
strspn計算經過幾個字元會在字串 s1 中遇到不屬於 s2 中的字元size_t strspn(const char *s1, const char *s2);
strpbrk回傳在字串 s2 中的任何字元在 s1 第一次出現位置的指標char *strpbrk(const char *s1, const char *s2);
strrchr回傳在字串 s 中,字元 c 最後一次出現位置的指標char *strrchr(const char *s, int c);
strstr回傳在字串 s2 在 s1 第一次出現位置的指標char *strstr(const char *s1, const char *s2);
strtok以字串 s2 的內容切割 s1char *strtok(char *s1, const char *s2);




以下函數計算字串的長度

函數名稱功能函數原型
strlen計算字串的長度size_t strlen(const char *s);




以下函數為進行記憶體區塊操作之用

函數名稱功能函數原型
memcpy從 s2 所指向的資料複製 n 個字元到 s1void *memcpy(void *s1, const void *s2, size_t n);
memmove從 s2 所指向的資料複製 n 個字元到 s1void *memmove(void *s1, const void *s2, size_t n);
memcmp比較 s1 與 s2 前 n 個字元的資料int memcmp(const void *s1, const void *s2, size_t n);
memchr找出字元 c 在 s 前 n 個字元第一次出現的位置void *memchr(const void *s, int c, size_t n);
memset將 s 中前 n 個字元全部設定為 cvoid *memset(void *s, int c, size_t n);

2014年12月7日 星期日

Typedef

CC++程式語言中,typedef是一個關鍵字。它用來對一個資料類型取一個新名字。目的是為了使原始碼更易於閱讀和理解。
來看以下程式碼:
int coxes;
int jaffa;
...
coxes++;
...
if (jaffa == 10)
...
現在來看以下程式碼:
typedef int Apple;
typedef int Orange;
Apple coxes;
Orange jaffa;
...
coxes++;
...
if (jaffa == 10)
...
這兩段程式碼都做同樣的一件事。第二個例子使用了 typedef,使其更易於了解將要進行什麼。也就是一個變數包含關於蘋果的資訊,而另一個包含關於橘子的資訊。
再來一個例子:
struct var {
    int data1;
    int data2;
    char data3;
};
此處使用者定義一個資料類型 var
像這樣建立一個 var 類型的變數,程式碼必須寫為(注意,在 C++ 中宣告一個 struct 時,同時也隱含了 typedef,C 則沒有):
struct var a;

在例子的最末處加入一行語句:
typedef struct var newtype;
現在要建立類型 var 的變數時,程式碼可以寫為:
newtype a;
這樣就更容易閱讀了,因為不用再為每一個 var 類型的變數加上關鍵字 struct
也可以給陣列使用 typedef 宣告。
typedef BaseType NewType [arrSize];
這樣就可以在宣告一個 BaseType 類型和 arrSize 大小的新陣列時,將程式碼寫為:
NewType array;

Enum (Enumeration) 列舉


在C中列舉(Enumeration)型別,就是以關鍵字enum開始加上一個列舉名稱,並以大括號括住要群組管理的常數,例如:

enum Action{stop, sit, stand, walk, run};

上例中宣告一個列舉型態Action,大括號中每一個元素稱為列舉元(enumerator),預設上列舉元從第一個開始的實際數值是0,然後依次遞 增,以上例而言,stop為0、sit為1、stand為2、walk為3、run為4,您也可以自行為列舉元設定數值,例如:

enum Action{stop = 1, sit, stand, walk, run};

上例來說,stop為1,下一個列舉元如果沒有設定數值的話,則自動遞增1,所以sit為2、stand為3、walk為4、run為5,列舉的常數值不 需獨一無二,例如下例:

enum Action{stop = 1, sit, stand = 2, walk, run};

下一個列舉元如果沒有設定數值的話,則自動遞增1,所以上例中stop為1、sit為2、stand為2、walk為3、run為4。

宣告列舉之後,您可以用它來宣告列舉變數,例如:

enum Action action = stop;

上例中action可接受的數值範圍為列舉元中的數值範圍,超出的話可能會有不可預期的行為。
在必要的時候,列舉元的數值可用來與其它數值作運算,例如:

enum Action action = stand;
printf("%d\n", action + 2);

列舉值的數值被取出再進行加2,所以上例中會在文字模式下顯示數值4。

2014年12月1日 星期一

eax,ebx,ecx,edx,esi,edi,ebp,esp 寄存器的作用


eax, ebx, ecx, edx, esi, edi, ebp, esp等都是X86 汇编语言中CPU上的通用寄存器的名称,是32位的寄存器。如果用C语言来解释,可以把这些寄存器当作变量看待。
比方说:add eax,-2 ;   //可以认为是给变量eax加上-2这样的一个值。
这些32位寄存器有多种用途,但每一个都有专长,有各自的特别之处。
EAX "累加器"(accumulator), 它是很多加法乘法指令的缺省寄存器。
EBX "基地址"(base)寄存器, 在内存寻址时存放基地址。
ECX 是计数器(counter), 是重复(REP)前缀指令和LOOP指令的内定计数器。
EDX 则总是被用来放整数除法产生的余数。
ESI/EDI 分别叫做"/目标索引寄存器"(source/destination index),因为在很多字符串操作指令中, DS:ESI指向源串,ES:EDI指向目标串.
EBP "基址指针"(BASE POINTER), 它最经常被用作高级语言函数调用的"框架指针"(frame pointer). 在破解的时候,经常可以看见一个标准的函数起始代码:

push ebp ; 保存当前ebp
mov ebp,esp ; EBP设为当前堆栈指针
sub esp, xxx ; 预留xxx字节给函数临时变量.
...

这样一来,EBP 构成了该函数的一个框架, EBP上方分别是原来的EBP, 返回地址和参数. EBP下方则是临时变量. 函数返回时作 mov esp,ebp/pop ebp/ret 即可.
ESP 专门用作堆栈指针,被形象地称为栈顶指针,堆栈的顶部是地址小的区域,压入堆栈的数据越多,ESP也就越来越小。 32位平台上,ESP每次减少4字节。
esp:寄存器存放当前线程的栈顶指针
ebp
:寄存器存放当前线程的栈底指针
eip
:寄存器存放下一个CPU指令存放的内存地址,当CPU执行完当前的指令后,从EIP寄存器中读取下一条指令的内存地址,然后继续执行。

一般寄存器:AXBXCXDX
AX:
累積暫存器,BX:基底暫存器,CX:計數暫存器,DX:資料暫存器

索引暫存器:SIDI 
SI:
來源索引暫存器,DI:目的索引暫存器 

堆疊、基底暫存器:SPBP 
SP:
堆疊指標暫存器,BP:基底指標暫存器 

EAX
ECXEDXEBX:為ax,bx,cx,dx的延伸,各為32位元 
ESI
EDIESPEBP:為si,di,sp,bp的延伸,32位元
栈的基本模型
参数N
高地址
参数
函数参数入栈的顺序与具体的调用方式有关
参数 3
参数 2
参数 1
EIP
返回本次调用后,下一条指令的地址
EBP
保存调用者的EBP,然后EBP指向此时的栈顶。
临时变量1

临时变量2

临时变量3

临时变量

临时变量5
低地址

條件運算符(三目运算符) & 逗號運算符

條件運算符(conditional operator?:)是一個基本的條件語句. 條件運算符最初起源於BCPL程式語言.

?: 的一般形式為
<表达式1> ? <表达式2> : <表达式3> 
其中表達式1為布爾表達式, 或真或為假. 若表達式1為真, 則返回表達式2的值作為整個條件表達式的值. 若條件為假, 則返回表達式3的值. 例如
max = (a > b) ? a : b ;
的執行結果就是將 a 和 b 中最大值賦給 max.



逗號運算子:,

c语言提供一种特殊的运算符,逗号运算符,优先级别最低,它将两式联接起来,如:(3+5,6+8)称为逗号表达式,其求解过程先表达式1,后表达式2,整个表达式值是表达式2的值,如:(3+5,6+8)的值是14。(a=3*5,a*4)的值是60。


表达式1,表达式2,表达式3,...... ,表达式n
逗号表达式的要领:
(1) 逗号表达式的运算过程为:从左往右逐个计算表达式。
(2) 逗号表达式作为一个整体,它的值为最后一个表达式(也即表达式n)的值。
(3) 逗号运算符的优先级别在所有运算符中最低。