C語言一些細節

1、gets()方法

gets()函數是用來接受一個字符串的函數,此方法接受一個string類型參數,但是卻沒有檢測此數值是否 有足夠的空間來拷貝數據。由於gets()無法知道字符串的大小,必須遇到換行字符或文件尾纔會結束輸入,因此容易造成緩存溢出的安全性問題。建議使用fgets()取代。現在用個小程序說明一下到底有什麼危害。

  #include <stdio.h>
  int main(void)
  {
      char a;
      char name[3];
      char b;
      scanf("%c",&a);
      printf("a:%c\n",a);            //第一次輸出a的值
      getchar();
      gets(name);
     scanf("%c",&b);                
     printf("name:%s;\na:%c;\nb:%c;\n",name,a,b);    //輸出所有的值,注意a
 }

運行結果如下:
這裏寫圖片描述
增加一下字符串長度試試。
這裏寫圖片描述
把字符串長度增加後,a的值莫名其妙被替換了,隨後編輯器就不能正常工作了。
如例子中看到的一樣,如果不能正確使用gets()造成的危害還是挺大的,會造成緩存溢出,覆蓋其他位置的值。所以出於安全考慮,應該儘量用fgets()代替gets()。

2、strcpy() 方法

strcpy() 是用來複制字符串的函數。通過小程序看看strcpy() 的功能。

  #include<stdio.h>
  #include<string.h>
  #define MAX 20

  int main(void)
  {
      char a[MAX]="abc";
      char b[MAX]="abcdefghi";
      strcpy(a,b);
     puts(a);
     puts(b);
 } 

運行結果如下:
這裏寫圖片描述
 此函數中還有兩個高級屬性——
  ①:它是char *類型,它返回的是第一個參數的值,即一個字符的地址。
  ②:第一個參數不需要指向數組的開始。
先附上代碼來說明這兩個屬性:

  #include<stdio.h>
  #include<string.h>
  #define MAX 40

  int main(void)
  {
      char *a="beast";
      char b[MAX]="you are the beast one.";
      char *p;
     p=strcpy(b+8,a);
     puts(a);
     puts(b);
     puts(p);
 } 

b+8的位置是the的第一個字母t,
看到這個代碼你是不是認爲結果如下:
beast
you ate beasteast one
beasteast one
結果呢?
這裏寫圖片描述
爲什麼會這樣呢?
我們再來看看strcpy()函數的功能:將一個字符串複製到另一個字符串。這個代碼無疑是把字符串a複製到b的第八個位置之後。
但是字符串的特性是什麼呢?字符串最後一個字節存放的是一個空字符——“\0”,用來表示字符串的結束。把a複製到b之後會令a的空字符把複製後的字符串隔斷,所以會顯示到beast就會結束。
而輸出p怎麼會輸出beast呢?這就是此函數的第一個屬性,此函數會返回複製之後的字符串的首地址。所以還是還是字符串。
可能到這裏你已經發現了一些問題,如果想把一個字符串的一部分複製到另一個字符串的某個位置,該怎麼辦呢,顯然strcpy()函數是滿足不了這個功能的,strncpy()函數是爲了彌補strcpy()函數不能檢查目標字符串是否容納下源字符串的不足而設定的一個函數。並且完全可以實現這個功能。

  #include<stdio.h>
  #include<string.h>
  #define MAX 30

  int main(void)
  {
      char *a="abcdefg";
      char b[MAX]="you are the beast one.";
      strncpy(b+4,a,3);
     puts(b);
 } 

這段代碼把字符串a的前三個字符賦值到b的第五個位置之後,所以結果如下:
這裏寫圖片描述

3、main() 方法的返回類型

Q:請問下面這段代碼能否通過編譯?如果能的話,那麼這段代碼中隱含什麼問題嗎?

#include<stdio.h>
void main(void)
{
    char *ptr = (char*)malloc(10);
    if(NULL == ptr)
    {
        printf("\n Malloc failed \n");
        return;
    }
    else
    { 
// Do some processing
        free(ptr);
    }
    return;
}

A:答案是代碼能通過編譯,但是會留下針對main()方法的返回類型的警告。main()方法的真正返回類型應該爲’int’而非’void’。這是因爲’int’返回類型能夠讓程序返回狀態值。尤其是當這段程序作爲其他應用的附屬程序時這個狀態值將更加重要。

4、內存泄漏

#include<stdio.h>
void main(void)
{
    char *ptr = (char*)malloc(10);
    if(NULL == ptr)
    {
        printf("\n Malloc failed \n");
        return;
    }
    else
    {  
// Do some processing
    }
    return;
}

A:好,雖然上面的代碼沒有對指針 ptr 進行內存釋放,但實際上即使是程序結束也不會造成內存泄露,因爲當程序結束時所有一開始被佔據的內存就全部清空了。但如果上面這段代碼是在 while 循環裏面那將會造成嚴重的問題
Note: 如果你需要了解更多關於內存泄露的問題以及如何使用工具檢測內存泄露,你可以參考這篇文章 Valgrind

5、free()方法

Q:以下代碼當用戶輸入’freeze’時會奔潰,而如果輸入’zebra’則運行正常,這是爲什麼?

#include<stdio.h>
int main(int argc, char *argv[])
{
    char *ptr = (char*)malloc(10);
    if(NULL == ptr)
    {
        printf("\n Malloc failed \n");
        return -1;
    }
    else if(argc == 1)
    {
        printf("\n Usage  \n");
    }
    else
    {
        memset(ptr, 0, 10);
        strncpy(ptr, argv[1], 9);
        while(*ptr != 'z')
        {
            if(*ptr == '')
                break;
            else
                ptr++;
        }
        if(*ptr == 'z')
        {
            printf("\n String contains 'z'\n");
// Do some more processing
        }
       free(ptr);
    }
    return 0;
}

A:問題的根源是因爲代碼在while循環中改變了 ptr 指針的地址。當輸入爲’zebra’時,while循環甚至在執行 第一遍前就結束了,所以free()釋放的內存地址就是一開始malloc()分配的地址。但是當輸入’freeze’時, ptr記錄的地址在while循環中被更改,因爲將會是錯誤的地址傳遞到free()方法中引起崩潰。

6、atexit with _exit

Q:在以下代碼,atexit()方法並沒有被調用,你知道爲什麼嗎?

#include<stdio.h>
void func(void)
{
    printf("\n Cleanup function called \n");
    return;
}
int main(void)
{
    int i = 0;

    atexit(func);

    for(;i<0xffffff;i++);

    _exit(0);
}

A:這是因爲使用了_exit() 方法。此方法並沒有調用清除數據相關的方法,比如 atexit()等。

7、void* 與 C 結構體

Q:能否設計一個方法接受任意類型的參數然後返回整數?同時是否有辦法傳遞多個這樣的參數?

A:一個能接受任意類型參數的方法像下面這個樣子:

int func(void *ptr)

如果需要傳遞多個參數,那麼我們可以傳遞一個包含這些參數的結構體

8、* 與 ++ 操作符

Q:以下代碼將輸出什麼?爲什麼?

#include<stdio.h>

int main(void)
{
    char *ptr = "Linux";
    printf("\n [%c] \n",*ptr++);
    printf("\n [%c] \n",*ptr);

    return 0;
}

A:以上的輸出將是:

因爲++與 * 的優先級一樣,所以 *ptr++ 將會從右向左操作。按照這個邏輯,ptr++ 會先執行然後執行*ptr。所以第一個結果是’L’。也因爲 ++ 被執行了,所以下一個printf() 結果是’i’。

9、Making changes in Code segment

Q:以下代碼運行時一定會崩潰,你能說出原因嗎?

#include<stdio.h>

int main(void)
{
    char *ptr = "Linux";
    *ptr = 'T';

    printf("\n [%s] \n", ptr);

    return 0;
}

A:這是因爲,通過 *ptr = ‘T’,此行代碼嘗試更改只讀內存存儲的字符串’Linux’。此操作當然行不通所以纔會造成崩潰。

10、Process that changes its own name

Q:你能否寫一個程序在它運行時修改它的名稱?

#include<stdio.h>

int main(int argc, char *argv[])
{
    int i = 0;
    char buff[100];

    memset(buff,0,sizeof(buff));

    strncpy(buff, argv[0], sizeof(buff));
    memset(argv[0],0,strlen(buff));

    strncpy(argv[0], "NewName", 7);


// Simulate a wait. Check the process

// name at this point.
    for(;i<0xffffffff;i++);

    return 0;
}

11、局部變量的返回地址

Q:下面的代碼有問題嗎?如果有,如何修改?

#include<stdio.h>

int* inc(int val)
{
  int a = val;
  a++;
  return &a;
}

int main(void)
{
    int a = 10;

    int *val = inc(a);

    printf("\n Incremented value is equal to [%d] \n", *val);

    return 0;
}

A:雖然上面的代碼有時運行會很好,但是在方法 inc() 中有很嚴重的隱患。當inc()方法執行後,再次使用局部變量的地址就會造成不可估量的結果。解決之道就是傳遞變量a的地址給main()。

12、處理 printf() 參數

Q:以下代碼輸出請問是什麼?

#include<stdio.h>

int main(void)
{
    int a = 10, b = 20, c = 30;

    printf("\n %d..%d..%d \n", a+b+c, (b = b*2), (c = c*2));

    return 0;
}

A:輸出將是

110..40..60

這是因爲參數都是從右向左處理的,然後打印出來卻是從左向右。
參考鏈接

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章