12个有趣的C语言问答(详解)
本文参照博文《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语言问答(详解)的更多相关文章
- 《12个有趣的C语言问答》(4)
C语言面试问答——<12个有趣的C语言问答>评析(4) 前文链接:http://www.cnblogs.com/pmer/p/3324063.html 8,Making changes i ...
- 《12个有趣的C语言问答》评析2
<12个有趣的C语言问答>评析(2) 前文链接:http://www.cnblogs.com/pmer/p/3313913.html (没存盘,遭遇过热保护.至少4个问答的评论白写了.默哀 ...
- 12个滑稽的C语言面试问答——《12个有趣的C语言问答》评析(5)
前文链接:http://www.cnblogs.com/pmer/archive/2013/09/17/3327262.html A,局部变量的返回地址 Q:下面的代码有问题吗?如果有,如何修改? # ...
- 12个有趣的C语言问答
转自:http://www.admin10000.com/document/913.html 1,gets() 方法 Q:以下代码有个被隐藏住的问题,你能找到它吗? 1 2 3 4 5 6 7 8 9 ...
- 12个有趣的c语言面试题
1.gets()函数 问:请找出下面代码里的问题: #include int main(void) { char buff[10]; memset(buff,0,sizeof(buff)); gets ...
- 深入理解C语言 - 指针详解
一.什么是指针 C语言里,变量存放在内存中,而内存其实就是一组有序字节组成的数组,每个字节有唯一的内存地址.CPU 通过内存寻址对存储在内存中的某个指定数据对象的地址进行定位.这里,数据对象是指存储在 ...
- c++、Java、python对应的编译型语言和解释性语言区别详解
1.首先明确一点: 高级语言是不能直接在CPU上运行的.CPU只能处理机器语言,就是黑客帝国里面那个10101010101110的数字流. 那么为了让机器语言能够在CPU上运行,那么就必须将其变成机器 ...
- 12个有趣的C语言面试题
摘要:12个C语言面试题,涉及指针.进程.运算.结构体.函数.内存,看看你能做出几个! 1.gets()函数 问:请找出下面代码里的问题: #include<stdio.h> int ma ...
- C语言关键字详解
相对于其他语言来说,C语言的关键字算是少的了.在C98中关键子总共只有32个,我们来分析一下每个关键字在C语言中它独特的作用. 1.关于数据类型的关键字 (1) char :声明字符型变量或函数 ( ...
随机推荐
- cocos 3.0 一键打包android平台应该注意的细节
cocos2d-x 移植越来越便捷,走到cocos2d-x-3.0rc2,能够说移植已经非常完好了,我们仅仅要进行适当的适配,cocos能够直接帮助我们生成apk 我网络不好无法上传图片:(无图无捷豹 ...
- Mysql 培训
1. Mysql 培训 1.1. 培训目的 本文档是针对MySQL 数据库方面的基础培训,为了使项目组成员能够达到使用MySQL 数据库的目的. 1.2. 培训对象 开发者 1.3. 经常使用词及符 ...
- [MODx] 10. Using Babel for Muti-languages support
1. Go to 'Extras' -> download and install 'Babel'. 2. Set up '.htaccess' file, currently, we set ...
- UIAlertController 简单修改title以及按钮的字体颜色
苦逼的开发者,最终败给了一个任性的UI,系统原生UIAlertController的按纽颜色必须改.于是,开始了不归路.之前的版本是自己用view写的一个仿系统UIActionSheet,动画感觉都挺 ...
- 文件I/O(不带缓冲)之原子操作
一.添写至一个文件 考虑一个进程,它要将数据添加到一个文件尾端.早期的UNIX系统并不支持open的O_APPEND选项,所以程序被编写成下列形式: ) < ) /* position to E ...
- PAT 1010
1010. Radix (25) Given a pair of positive integers, for example, 6 and 110, can this equation 6 = 11 ...
- 在android的spinner中,实现取VALUE值和TEXT值
为了实现在android的spinner实现取VALUE值和TEXT值,我尝试过好些办法,在网上查的资料,都是说修改适配器,刚开始我也是通过修改适配器的方法来做的,但是如果一个activity有多个s ...
- 打造强大的BaseModel(1):让Model自我描述
前言 从事iOS开发已经两年了,从一无所知到现在能独立带领团队完成一系列APP的开发,网络上的大神给了我太多的帮助.他们无私地贡献自己的心得和经验,写出了一篇篇精美的文章.现在我也开始为大家贡献自己的 ...
- Android NDK调试方式之一: adb logcat
查看程序执行过程中所打印的log信息,用于辅助调试排除代码错误. 一.采用NDK安装包下Samples/hello-jni工程做实验 1)修改jni/hello-jni.c文件 #include &l ...
- Phantomjs安装
环境:Centos 6.5 介绍:PhantomJS 是一个基于 WebKit 的服务器端 JavaScript API.它全面支持web而不需浏览器支持,其快速,原生支持各种Web标准: DOM 处 ...