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 

这是由于参数都是从右向左处理的,而后打印出来倒是从左向右。

相关文章
相关标签/搜索