12個有趣的C語言問答(詳解)

本文參照博文《12個有趣的C語言問答》,在原文的基礎上增長來對應的知識點的詳細介紹。ios

1 gets()方法數組

Q:下面的代碼有一個被隱藏的問題,你能找到它嗎?安全

 1 #include <stdio.h>
 2 
 3 int main(void)
 4 {
 5     char buff[10];
 6     memset(buff, 0, sizeof(buff));
 7     gets(buff);
 8     printf("%s\n", buff);
 9 
10     return 0;
11 }

A:這個不顯眼的問題就是使用了gets()方法,其函數原型以下:函數

char* gets(char *s);

此方法接受一個字符數組參數,可是卻沒有檢查此數組是否有足夠的空間來拷貝數據。gets()函數是不安全的,不推薦使用,通常狀況下編譯器也會給出警告提示:the `gets' function is dangerous and should not be used。gets()不檢查預留存儲區是否可以容納實際輸入的數據。多出來的字符簡單的溢出到相鄰的存儲區,可能會致使錯誤。測試

因此,這裏咱們通常用fgets()方法更好,函數原型以下:this

char* fgets(char *s, int n, FILE *stream); 

通常使用fgets()函數,都是讀取文件當中的n-1個字符到s中,其實,此函數還有一個很好的用處就是從標準輸入流中讀取字符串,並且不用擔憂輸入的字符個數超出了字符數組的大小而致使溢出的問題!要怎樣作呢?以下:spa

char str[10];  
fgets(str, siezof(str), stdin); 

值得注意的是:謹記fgets()只讀取n-1個字符因此,fgets()讀取到換行符、文件尾或讀完n-1個字符便會進行返回。.net

 

2 strcpy()方法設計

Q:密碼防禦是很基本的功能,看看可以搞定下面這一段代碼?指針

 1 #include <stdio.h>
 2 #include <memory.h>
 3 int main(int argc, char *argv[])
 4 {
 5     int flag = 0;
 6     char passwd[10];
 7 
 8     memset(passwd, 0, sizeof(passwd));
 9     strcpy(passwd, argv[1]);
10 
11     if (0 == strcmp("LinuxGeek", passwd)){
12         flag = 1;
13     }
14     if (flag){
15         printf("\n Password cracked \n");
16     }else{
17         printf("\n Incorrect password \n");
18     }
19 
20     return 0;
21 }

說明:該程序經過在運行時攜帶一個密碼參數,而後程序會將用戶輸入的密碼參數值與真實的密碼比較,若是二者相等就輸出cracked信息,不然輸出incorrect提示。

 

3 main()函數的返回類型

Q:請問下面這段代碼可否經過編譯?若是能的話,那麼這段代碼中隱含什麼問題嗎?

#include <stdio.h>
#include <stdlib.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返回類型能夠返回程序運行的狀態值,尤爲是當這段程序做爲其餘應用的附屬程序時這個狀態值將更加劇要。

mainret.c:3:6: warning: return type of ‘main’ is not ‘int’ [-Wmain]
 void main(void)
      ^

 

4 內存泄漏

Q:請問,如下代碼有內存泄漏嗎?

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

    return 0;
}

A:不會,雖然上面的代碼沒有對指針ptr進行內存釋放,但實際上即便是程序結束也不會形成內存泄漏,由於當程序結束時全部一開始被佔據的內存就所有清空了。可是,若是上面分配內存這段代碼是在while循環裏面那將會形成嚴重的問題。

 

5 free()方法

Q:如下代碼,當用戶輸入'freeze'時會崩潰,而若是輸入'zebra'則運行正常,爲何?

 1 #include <stdio.h>
 2 #include <stdlib.h>
 3 #include <memory.h>
 4 int main(int argc, char *argv[])
 5 {
 6     char *ptr = (char *)malloc(10);
 7 
 8     if (NULL == ptr){
 9         return -1;
10     }
11     if (argc == 1){
12         printf("\n Usage: add a string \n");
13     }else {
14         memset(ptr, 0, 10);
15         strncpy(ptr, argv[1], 9);
16         while (*ptr != 'z'){
17             if (*ptr == ' ') break;
18             else ptr++;
19         }
20         if (*ptr == 'z'){
21             printf("\n String contains 'z' \n");
22             //Do some more processing
23         }
24         free(ptr);
25     }
26 
27     return 0;
28 }

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

注意:調用free()方法釋放內存時,參數必需要麼是NULL,要麼是先前從malloc/calloc或者realloc返回的地址,不能將一次動態申請的內存的部分釋放。

 

6 atexit()和_exit()

Q:如下代碼中的atexit()方法並無被調用,直到爲何嗎?

#include <stdio.h>
#include <unistd.h>

void func(void)
{
    printf("\n Clean up function called \n");
}

int main(void)
{
    int i = 0;

    atexit(func);
    for (; i < 0xFFFF; i++);
    _exit(0);
}

A:這是由於使用了 _exit() 方法。此方法並無調用清除數據相關的方法,好比 atexit()等。exit和_exit都是用來正常終止一個進程的,主要區別是_exit會馬上進入內核,而exit先執行一些清除工做(包括執行各類終止處理程序,關閉全部標準I/O等,一旦關閉了IO,例如printf等函數就不會輸出任何東西了),而後才進入內核。這兩個函數會對父子進程有必定的影響,當用vfork建立子進程時,子進程會先在父進程的地址空間運行(這跟fork不同),若是子進程調用了exit就會把父進程的IO給關掉。

 

補充:還有一種在程序退出前執行相應函數的方法,就是調用<stdlib.h>中提供的_onexit()回調函數,用法以下:

 1 #include <iostream>
 2 #include <string>
 3 #include <stdlib.h>
 4 
 5 using namespace std;
 6 
 7 int fun(void){
 8     cout << "Exit Function\n";
 9     return 0;
10 }
11 
12 int main()
13 {
14     _onexit(fun);
15     cout << "Finished.\n";
16     return 0;
17 }

須要注意的是,回調函數要求返回值必須是int類型,不然會報錯!

 

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:程序的輸出結果以下:

 [L] 

 [i] 

由於++與 * 的優先級同樣,因此 *ptr++ 將會從右向左操做。按照這個邏輯,ptr++ 會先執行而後執行*ptr。因此第一個結果是'L'。也由於 ++ 被執行了,因此下一個printf() 結果是'i'。

9 Making changes in code segment

Q:如下代碼運行時必定會崩潰,你能說出緣由嗎?

1 #include <stdio.h>
2 int main(void)
3 {
4     char *ptr = "Linux";
5     *ptr = 'T';
6     printf("\n [%s] \n", ptr);
7 
8     return 0;
9 }

A:這是由於字符串常量「Linux」是以只讀的形式存儲的,而經過*ptr='T'語句,此代碼嘗試更改只讀內存存儲的字符串內容,此操做固然行不通,因此纔會致使崩潰。

 

10 Process that changes its own name

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

A:如下的代碼能夠:

 1 #include <stdio.h>
 2 #include <memory.h>
 3 
 4 int main(int argc, char *argv[])
 5 {
 6     int i = 0;
 7     char buff[100];
 8 
 9     memset(buff, 0, sizeof(buff));
10     strncpy(buff, argv[0], sizeof(buff));
11 
12     memset(argv[0], 0, strlen(buff));
13     strncpy(argv[0], "NewName", 7);
14     //Simulate a wait. Check the process name at this point
15     for (; i < 0xFFFFFFFF; i++);
16 
17     return 0;
18 }

能夠經過下面的方法測試

$ gcc chname.c -o chname
$ ./chname &
[1] 4677
$ ps 4677
  PID TTY      STAT   TIME COMMAND
 4677 pts/11   R      0:08 NewName 

 

11 局部變量的返回地址

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

 1 #include <stdio.h>
 2 int* inc(int val)
 3 {
 4     int a = val;
 5     a++;
 6     return &a;
 7 }
 8 
 9 int main(void)
10 {
11     int a = 10;
12     int *val = inc(a);
13     printf("\n Increamented value is equal to [%d] \n", *val);
14 
15     return 0;
16 } 

A:雖然上面的代碼有時運行會很好,可是在方法 inc() 中有很嚴重的隱患,由於它返回了局部變量的地址。當inc()方法執行後,再次使用局部變量的地址就會形成不可估量的結果。解決之道就是傳遞變量a的地址給main()。PS:我以爲最後一句的說法有問題。

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 

這是由於參數都是從右向左處理的,而後打印出來倒是從左向右。

相關文章
相關標籤/搜索