本文参照博文《12个有趣的C语言问答》,在原文的基础上增加来对应的知识点的详细介绍。

1 gets()方法

Q:下面的代码有一个被隐藏的问题,你能找到它吗?

 #include <stdio.h>

 int main(void)
{
char buff[];
memset(buff, , sizeof(buff));
gets(buff);
printf("%s\n", buff); return ;
}

A:这个不显眼的问题就是使用了gets()方法,其函数原型如下:

char* gets(char *s);

此方法接受一个字符数组参数,但是却没有检查此数组是否有足够的空间来拷贝数据。gets()函数是不安全的,不推荐使用,一般情况下编译器也会给出警告提示:the `gets' function is dangerous and should not be used。gets()不检查预留存储区是否能够容纳实际输入的数据。多出来的字符简单的溢出到相邻的存储区,可能会导致错误。

所以,这里我们一般用fgets()方法更好,函数原型如下:

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

一般使用fgets()函数,都是读取文件当中的n-1个字符到s中,其实,此函数还有一个很好的用处就是从标准输入流中读取字符串,而且不用担心输入的字符个数超出了字符数组的大小而导致溢出的问题!要怎样做呢?如下:

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

值得注意的是:谨记fgets()只读取n-1个字符所以,fgets()读取到换行符、文件尾或读完n-1个字符便会进行返回。

2 strcpy()方法

Q:密码防护是很基本的功能,看看能够搞定下面这一段代码?

 #include <stdio.h>
#include <memory.h>
int main(int argc, char *argv[])
{
int flag = ;
char passwd[]; memset(passwd, , sizeof(passwd));
strcpy(passwd, argv[]); if ( == strcmp("LinuxGeek", passwd)){
flag = ;
}
if (flag){
printf("\n Password cracked \n");
}else{
printf("\n Incorrect password \n");
} return ;
}

说明:该程序通过在运行时携带一个密码参数,然后程序会将用户输入的密码参数值与真实的密码比较,如果两者相等就输出cracked信息,否则输出incorrect提示。

3 main()函数的返回类型

Q:请问下面这段代码能否通过编译?如果能的话,那么这段代码中隐含什么问题吗?

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

A:代码能通过编译,但是会留下针对main()函数返回值类型的警告。main()函数的真正返回值类型应该是int而不是void,这是因为int返回类型可以返回程序运行的状态值,尤其是当这段程序作为其他应用的附属程序时这个状态值将更加重要。

mainret.c::: 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();
if (NULL == ptr){
printf("\n Malloc failed \n");
return -;
}else{
//Do some processing
} return ;
}

A:不会,虽然上面的代码没有对指针ptr进行内存释放,但实际上即使是程序结束也不会造成内存泄漏,因为当程序结束时所有一开始被占据的内存就全部清空了。但是,如果上面分配内存这段代码是在while循环里面那将会造成严重的问题。

5 free()方法

Q:以下代码,当用户输入'freeze'时会崩溃,而如果输入'zebra'则运行正常,为什么?

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

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 = ; atexit(func);
for (; i < 0xFFFF; i++);
_exit();
}

A:这是因为使用了 _exit() 方法。此方法并没有调用清除数据相关的方法,比如 atexit()等。exit和_exit都是用来正常终止一个进程的,主要区别是_exit会立刻进入内核,而exit先执行一些清除工作(包括执行各种终止处理程序,关闭所有标准I/O等,一旦关闭了IO,例如printf等函数就不会输出任何东西了),然后才进入内核。这两个函数会对父子进程有一定的影响,当用vfork创建子进程时,子进程会先在父进程的地址空间运行(这跟fork不一样),如果子进程调用了exit就会把父进程的IO给关掉。

补充:还有一种在程序退出前执行相应函数的方法,就是调用<stdlib.h>中提供的_onexit()回调函数,用法如下:

 #include <iostream>
#include <string>
#include <stdlib.h> using namespace std; int fun(void){
cout << "Exit Function\n";
return ;
} int main()
{
_onexit(fun);
cout << "Finished.\n";
return ;
}

需要注意的是,回调函数要求返回值必须是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 ;
}

A:程序的输出结果如下:

 [L] 

 [i] 

因为++与 * 的优先级一样,所以 *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 ;
}

A:这是因为字符串常量“Linux”是以只读的形式存储的,而通过*ptr='T'语句,此代码尝试更改只读内存存储的字符串内容,此操作当然行不通,所以才会导致崩溃。

10 Process that changes its own name

Q:你能否写一个程序,在它运行时修改它的名称?

A:以下的代码可以:

 #include <stdio.h>
#include <memory.h> int main(int argc, char *argv[])
{
int i = ;
char buff[]; memset(buff, , sizeof(buff));
strncpy(buff, argv[], sizeof(buff)); memset(argv[], , strlen(buff));
strncpy(argv[], "NewName", );
//Simulate a wait. Check the process name at this point
for (; i < 0xFFFFFFFF; i++); return ;
}

可以通过下面的方法测试

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

11 局部变量的返回地址

Q:下面的代码有问题吗?如果有,如何修改?

 #include <stdio.h>
int* inc(int val)
{
int a = val;
a++;
return &a;
} int main(void)
{
int a = ;
int *val = inc(a);
printf("\n Increamented value is equal to [%d] \n", *val); return ;
}

A:虽然上面的代码有时运行会很好,但是在方法 inc() 中有很严重的隐患,因为它返回了局部变量的地址。当inc()方法执行后,再次使用局部变量的地址就会造成不可估量的结果。解决之道就是传递变量a的地址给main()。PS:我觉得最后一句的说法有问题。

12 处理printf()参数

Q:请问以下代码的输出是什么?

#include<stdio.h>

int  main( void )
{
int a = , b = , c = ; printf ("\n %d..%d..%d \n", a+b+c, (b = b*), (c = c*));
return ;
}

A:程序的输出如下:

 .... 

这是因为参数都是从右向左处理的,然后打印出来却是从左向右。

12个有趣的C语言问答(详解)的更多相关文章

  1. 《12个有趣的C语言问答》(4)

    C语言面试问答——<12个有趣的C语言问答>评析(4) 前文链接:http://www.cnblogs.com/pmer/p/3324063.html 8,Making changes i ...

  2. 《12个有趣的C语言问答》评析2

    <12个有趣的C语言问答>评析(2) 前文链接:http://www.cnblogs.com/pmer/p/3313913.html (没存盘,遭遇过热保护.至少4个问答的评论白写了.默哀 ...

  3. 12个滑稽的C语言面试问答——《12个有趣的C语言问答》评析(5)

    前文链接:http://www.cnblogs.com/pmer/archive/2013/09/17/3327262.html A,局部变量的返回地址 Q:下面的代码有问题吗?如果有,如何修改? # ...

  4. 12个有趣的C语言问答

    转自:http://www.admin10000.com/document/913.html 1,gets() 方法 Q:以下代码有个被隐藏住的问题,你能找到它吗? 1 2 3 4 5 6 7 8 9 ...

  5. 12个有趣的c语言面试题&nbsp;

    1.gets()函数 问:请找出下面代码里的问题: #include int main(void) { char buff[10]; memset(buff,0,sizeof(buff)); gets ...

  6. 深入理解C语言 - 指针详解

    一.什么是指针 C语言里,变量存放在内存中,而内存其实就是一组有序字节组成的数组,每个字节有唯一的内存地址.CPU 通过内存寻址对存储在内存中的某个指定数据对象的地址进行定位.这里,数据对象是指存储在 ...

  7. c++、Java、python对应的编译型语言和解释性语言区别详解

    1.首先明确一点: 高级语言是不能直接在CPU上运行的.CPU只能处理机器语言,就是黑客帝国里面那个10101010101110的数字流. 那么为了让机器语言能够在CPU上运行,那么就必须将其变成机器 ...

  8. 12个有趣的C语言面试题

    摘要:12个C语言面试题,涉及指针.进程.运算.结构体.函数.内存,看看你能做出几个! 1.gets()函数 问:请找出下面代码里的问题: #include<stdio.h> int ma ...

  9. C语言关键字详解

    相对于其他语言来说,C语言的关键字算是少的了.在C98中关键子总共只有32个,我们来分析一下每个关键字在C语言中它独特的作用. 1.关于数据类型的关键字 (1) char :声明字符型变量或函数  ( ...

随机推荐

  1. 【转】简明vim练级攻略

    本文来自:http://coolshell.cn/articles/5426.html vim的学习曲线相当的大(参看各种文本编辑器的学习曲线),所以,如果你一开始看到的是一大堆VIM的命令分类,你一 ...

  2. CentOS 6.5安装TortoiseSVN svn client

    TortoiseSVN: TortoiseSVN 是 Subversion 版本号控制系统的一个免费开源client,能够超越时间的管理文件和文件夹. 文件保存在中央版本号库,除了能记住文件和文件夹的 ...

  3. ORM之一:适合我的ORM

    一.常见开源ORM框架 比喻:Kerosene ORM,DbLinq,Dapper,DynamicQuery,elinq,glinq,NPoco,Relinq,EF,ServiceStack.OrmL ...

  4. CustomViewWith_Image_Text_Video

    CustomViewOfTextVideoImage.rar https://github.com/Grishu/CustomViewWith_Image_Text_Video

  5. LeetCode144:Binary Tree Preorder Traversal

    Given a binary tree, return the preorder traversal of its nodes' values. For example: Given binary t ...

  6. Swift数据类型

    1.Swift中常用数据类型:首字母大写 Int.Float.Double.Bool.Character.String Array.Dictionary.元组类型(Tuple).可选类型Optiona ...

  7. Android_Toast

    xml文件: main1: <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" ...

  8. 【转】SQL Server 2012 配置AlwaysOn(三)

    转载自:http://www.cnblogs.com/lyhabc/p/4682986.html 从0开始搭建SQL Server AlwaysOn 第三篇(配置AlwaysOn) 第一篇http:/ ...

  9. JS获取活动区域高和宽

    var width;            var height;            //获取窗口宽度            if (window.innerWidth)              ...

  10. /lib /usr/lib /usr/local/lib 区别

    简单说,/lib是内核级的,/usr/lib是系统级的,/usr/local/lib是用户级的. /lib/ — 包含许多被 /bin/ 和 /sbin/ 中的程序使用的库文件.目录 /usr/lib ...