8451|26

6366

帖子

4929

TA的资源

版主

楼主
 

小话c语言 [复制链接]

作者:陈曦,
转载自CSDN博客


该系列共有25篇,总共300多页,我已将该资源整理成文档形式上传到“下载中心”,大家可以到“下载中心”下载该资源


前言----小话c语言(1)
不知道该怎么开头,不过开头的几个字都写了,就继续写下去吧。
    看过很多以大话开头的书籍,觉得也不怎么样,觉得还没达到大话的层次,本人本着谦虚的精神,暂且以小话开头吧;可能读者看完,觉得连小话都谈不上,先不管这些了;如果读者确实都觉得连小话都谈不上,到时候我再改个名字好了,这样至少也对得起文章的标题。
    言归正传,回到主题吧。
    以前写过关于c语言的学习资料,发现它真不是容易就能写好的,里面涉及到很多很多的东西;如果是以基础为目的的,那需要描述的就更多了;如果是稍微提高一些的,那么可能可以少写一些字。不过,以c语言的本质出发,实在太复杂了,如果让c语言之父来描述,估计那本书也没能足以表达他所有的精神和思想,毕竟核心思想还是在DennisRitchie心里,不过他已经离我们而去了,先默哀一下。

语言都是相通的
    c语言,也是一种语言,和我们中国人平时说的中文其实是类似的,只不过一个最终是给机器来理解的,一个是让人来理解的。我不知道哪种应该更复杂,但是,有一点是可以肯定的,是语言它的语法就不会太复杂,否则不要说是笨笨的机器,就是地球上应该是最高级的人类可能都不能很好地理解,如果这样,这种语言的存在价值就需要思考了。"你好",这句话表示的就是个问候语,如果非要细节地分析内部的语法,可以理解成主谓结构吧,"你"是主语,"好"当成谓语吧。"int i =1;" 理解成定义一个整形变量i; int表示变量类型, 后面跟着变量名,再跟着赋值号=,再跟着一个赋值的数据,最后以分号结束即可。这个结构可以用如下的表达式来表达:  
    类型  类型名 = 初值;
    上面的看起来,不是很复杂,就像理解"你从哪里来?", "我从这里来。"这样的话一样。

为什么需要编译器
     写完了int i = 1;之后,机器如何理解呢?当然,没有哪个机器能一下子理解这个。因为,有公司已经把机器的cpu设计成只能理解机器语言的了,那怎么办呢?只能用一种东西将上面的语句翻译成机器指令,传给机器的cpu,机器就能理解并执行了,而,这个东西就是编译器。不过,有人可能会说,我使用了bash终端,我输入ls -l再回车,就可以帮我执行命令?哪里有编译器?

解释器是什么
   上面说的那个过程真没用到编译器,而是解释器。其实解释器就做了编译器的事情,首先会解析输入的字符串ls-l, 就像编译器解析int i = 1;这个字符串一样,然后解析其中的语义,最后执行对应的操作。

解释器到底是什么
这个东西真的不用多想,它就是一个经过编译器编译ok的程序而已。

解释器运行程序会比对应编译器编译后的程序运行的慢
   说的基本是对的,一般是这样,因为解释器多了一层解释的过程,然后才执行。

结束语
    好了,关于解释器和编译器的比较就先到此为止,前言的内容我不想写一些废话,因为多打一句废话也是消耗能量的;
如果有写的有误的地方,敬请指出,可以留言或者发邮箱 511272827@qq.com  ,不甚感激。

[ 本帖最后由 tiankai001 于 2012-12-15 07:28 编辑 ]
此帖出自单片机论坛

最新回复

kankan  看看楼主的东东  详情 回复 发表于 2016-3-24 19:28

点评

好东西,,收藏!  详情 回复 发表于 2012-12-15 11:55
点赞 关注
 

回复
举报

6366

帖子

4929

TA的资源

版主

沙发
 

开头语
    上面一篇已经写过,c语言不仅仅只是语法很好玩,很让人捉摸不透,它的思想更是深奥,需要慢慢体会的。看了关于c语言的书籍不少于50本,每看一遍都感觉认识深了一点,但是终究还是无法完全理解它背后最深层的思想,只是在不断地向它走近。正因为如此,下面的描述不会按照一个固定的说明文格式来编写,而是采用对话的方式来将这个过程展现出来,这样应该能更多地展示出c语言的思想。
    格式: 问题(Question)将以 Q: 开头,  回答(Answer)将以  A: 作为开头。

Q: c语言的HelloWorld版本是什么样子的?
A: 起名为hello.c:
1.    #include   
2.      
3.    int main()  
4.    {  
5.        printf("Hello World!");  
6.        return 0;  
7.    }  


Q: #include这条语句是什么意思?
A: 它以#开头,它表示预处理过程,一般不把它当做编译过程,而是用一个单独的预处理器来处理。因为,从编译源代码的角度来说,将一个头文件插入到最终被编译的代码和编译源代码真的没什么一致的地方。

Q:如何才能感受预处理器的存在呢?
A: 一般的编译命令都提供预处理选项,可以查看预处理后的源代码是什么样子。
[Windows-VS2010] 可以使用cl.exe命令来查看预处理后的源代码:

cl    /P   hello.c


file:///C:/DOCUME~1/ADMINI~1/LOCALS~1/Temp/msohtml1/01/clip_image002.jpg
会在同目录下生成一个.i的预处理后的文件hello.i, 内容如下(因为篇幅问题,无法将所有内容上传,否则保存不了,省略号省略了大部分代码):
1.    #line 1 "hello.c"  
2.    #line 1 "d:\\Program Files\\Microsoft Visual Studio 10.0\\VC\\INCLUDE\\stdio.h"  
3.      
4.    .............................  
5.    .............................  
6.      
7.    #line 739 "d:\\Program Files\\Microsoft Visual Studio 10.0\\VC\\INCLUDE\\stdio.h"  
8.      
9.    #line 2 "hello.c"  
10.   
11.  int main()  
12.  {  
13.      printf("Hello World!");  
14.      return 0;  
15.  }  


Q:看了那个文件,文件实在是太长了!
A: 是的,这其实也是c和c++的一个缺点,#include将源代码插入的方式可能会导致被编译的源代码长度迅速增加,这也是预编译出现的一个原因。也可以看到其实hello.c代码那几行也就是最后的那几句,前面都是#include头文件插入的代码,确实大的很直接。
Q:hello.i文件最后几句,有#line 2 "hello.c"这句,它是什么意义?
A: #符号开头,依然是编译器的指令,它表示当前行被设置为2,文件名是hello.c.因为,之前的#include被插入了很多其它文件的内容,但是在hello.c中,第二行是接着#include 后面的,所以将行数重新设置,这样才能正确地解析hello.c的代码行数。

Q:cl命令我怎么知道/P参数是预处理作用的?
A:当然可以用cl /?命令来得到它的帮助,然后找到需要功能的参数。


file:///C:/DOCUME~1/ADMINI~1/LOCALS~1/Temp/msohtml1/01/clip_image004.jpg

Q: 这么多参数,查找要的有点难啊?
A: 可以想到unix下的grep命令来过滤字符串。当然,这需要cygwin的支持。


file:///C:/DOCUME~1/ADMINI~1/LOCALS~1/Temp/msohtml1/01/clip_image006.jpg
上面的grep命令是在装了cygwin后才有的。


file:///C:/DOCUME~1/ADMINI~1/LOCALS~1/Temp/msohtml1/01/clip_image008.jpg

Q: 再回到hello.c里面吧。main函数必须有个返回值吗?
A: 是的,其实这很能体现分层思想,任何一个应用程序最终都可能有个返回值,供调用它的程序来使用,所以在这里有个int的返回值
也是可以理解的。

Q: 怎么查看调用一个程序的返回值呢?
A: [Windows] 在命令行下,可以使用echo %errorlevel%来获取它的值。
[Mac]在命令行下,可以使用echo $?来获取。
[Ubuntu]同[Mac].
比如,hello.c的代码编译成hello.exe,
cl  hello.c:


file:///C:/DOCUME~1/ADMINI~1/LOCALS~1/Temp/msohtml1/01/clip_image010.jpg
生成hello.exe,运行它:


file:///C:/DOCUME~1/ADMINI~1/LOCALS~1/Temp/msohtml1/01/clip_image012.jpg
使用echo %errorlevel%打印返回值:


file:///C:/DOCUME~1/ADMINI~1/LOCALS~1/Temp/msohtml1/01/clip_image014.jpg
可以看到,返回的正是代码中return语句后面的0.

Q: 可以返回的不是0吗?
A: 当然可以。修改hello.c源代码为:
1.    #include   
2.      
3.    int main()  
4.    {  
5.        printf("Hello World!");  
6.        return 1;  
7.    }  


再次编译运行后,使用echo%errorlevel%得到:


file:///C:/DOCUME~1/ADMINI~1/LOCALS~1/Temp/msohtml1/01/clip_image016.jpg

Q: printf是一个函数,它的原型是什么?
A: 在stdio.h中可以看到它的原型是:


file:///C:/DOCUME~1/ADMINI~1/LOCALS~1/Temp/msohtml1/01/clip_image018.gif

Q: 怎么这么复杂?紫色的部分是什么?
A:  其实,很多原型中有一些附加的文本,它们在编译过程中并没有充当很有意义的东西,只是一种注释或者说明信息,在一些特定情况下起了作用,可以暂且不用特别关注它们。
比如,_Check_return_opt_的宏定义是:


file:///C:/DOCUME~1/ADMINI~1/LOCALS~1/Temp/msohtml1/01/clip_image020.jpg
再接着进入_Check_return_也可以看到类似的宏定义,依次分析。

Q: 那么printf函数的原型简化版就是
1.    int __cdecl printf(const char * _Format, ...);  


A:是的,也可以再简化成
1.    int  printf( const char * _Format, ...);  


Q: __cdecl是什么?
A: 这是一种函数调用方式;对应的还有__stdcall,__ fastcall等等,它们有一定的区别,比如函数压栈顺序,参数优先保存在寄存器中等,c语言默认是__cdecl方式。

Q: const char * _Format中的const可以去掉吗?
A: 可以的,但是用上const表示不可更改的变量,printf函数内部不可以改变_Format指向的字符串,这是一种防御性编程的思想。

Q: c++中的const不是表示常量吗?
A: 是的,但是c语言中的const表示的是一个变量,但是不可更改。这个关键字在c语言和c++中是有区别的。

Q: 举个例子。
A: 比如在c语言中用const修饰的一个变量,不可以作为静态数组的参数个数,因为它是一个变量;在c++中,这是可以的。
如hello.c代码修改为如下:
1.    #include   
2.      
3.    int main()  
4.    {  
5.        const int size = 10;  
6.        int arr[size];  
7.        return 0;  
8.    }  


出现编译错误:


file:///C:/DOCUME~1/ADMINI~1/LOCALS~1/Temp/msohtml1/01/clip_image022.jpg
错误原因就是需要一个常量的大小表示数组的大小。

Q:如果是c++,如何呢?
A: 使用cl   /TP    hello.c将hello.c代码当成c++代码进行编译:


file:///C:/DOCUME~1/ADMINI~1/LOCALS~1/Temp/msohtml1/01/clip_image024.jpg
没有出现错误。

Q: 源代码不是hello.c,后缀名是.c, 怎么是当做c++代码编译了呢?
A: 这主要是在于/TP参数的作用了:


file:///C:/DOCUME~1/ADMINI~1/LOCALS~1/Temp/msohtml1/01/clip_image026.jpg
可以看到,/TP命令行将所有文件当成.cpp来编译,即也会把hello.c代码当成.cpp来编译。

Q: 这么说来,编译器编译源代码不一定看后缀名的了?
A: 当然是的,对于编译器来说,只要给文本即可,对于后缀只不过在通常情况下按照指定类型代码编译而已,可以指定将
某种扩展名的代码当成特定类型代码编译的。

Q: printf函数原型参数中的三个点表示什么?
A: 它表示可变参数,即是参数个数不能确定,也许是1个,也许2个或者更多。

Q: 为什么要这么设计?
A: 因为对于输出功能来说,该输出多少东西,设计者开始是不知道的,所以交给了程序员来是实现,编译器只需要根据获取
到的参数最后正确转换给内部处理函数即可。
比如:
1.    printf("Hello World!");  
2.    printf("My name is %s", "xichen");  
3.    printf("My name is %s, age is %d", "xichen", 25);  


Q: 那么printf函数返回什么呢?

A: 它返回成功输出的字节数。
源代码:
1.    #include   
2.      
3.    int main()  
4.    {  
5.        int ret;  
6.        ret = printf("abc");  
7.        printf(" ret is %d\n", ret);  
8.        ret = printf("Hello World!");  
9.        printf(" ret is %d\n", ret);  
10.      ret = printf("My name is %s", "xichen");  
11.      printf(" ret is %d\n", ret);  
12.   
13.      return 0;  
14.  }  


输出:


file:///C:/DOCUME~1/ADMINI~1/LOCALS~1/Temp/msohtml1/01/clip_image028.jpg

Q:如果有中文在字符串中,字节数怎么算呢?
A: 这需要根据终端最终输出的字节数来得到了。
在Windows下的记事本打开编写如下代码:
1.    #include   
2.      
3.    int main()  
4.    {  
5.        int ret;  
6.        ret = printf("abc中国");  
7.        printf(" ret is %d\n", ret);  
8.      
9.        return 0;  
10.  }  


以ANSI格式保存,ANSI即以本地化编码格式保存;
[Windows7]本地化语言默认为ANSI格式,在注册表中查看得到:


file:///C:/DOCUME~1/ADMINI~1/LOCALS~1/Temp/msohtml1/01/clip_image030.jpg
是0804,0804代表简体中文,对应编码为GBK.
再次查看cmd输出字符使用的编码:


file:///C:/DOCUME~1/ADMINI~1/LOCALS~1/Temp/msohtml1/01/clip_image032.jpg
可以看到,确定编码格式是GBK.
所以,上面的字符串中的"中国"是以GBK格式输出,即一个中文字符对应2个字节,即"abc中国"这个字符串总长度为7.
所以,输出:


file:///C:/DOCUME~1/ADMINI~1/LOCALS~1/Temp/msohtml1/01/clip_image034.jpg

Q: 如果将此源代码另存为UTF8格式保存,进行编译,最终的结果会不会变呢?
A: 来看看。
将源代码以UTF8格式保存:


file:///C:/DOCUME~1/ADMINI~1/LOCALS~1/Temp/msohtml1/01/clip_image036.jpg
再次编译hello.c,并运行:


file:///C:/DOCUME~1/ADMINI~1/LOCALS~1/Temp/msohtml1/01/clip_image038.jpg
可以看到,它的结果依然是7.

Q:不是编码格式变成UTF8, 这两个中文每个字符占有3个字节吗?输出的字节数怎么还是7,它怎么认为每个中文占用2个字节呢?
把UTF8格式的hello.c源代码用十六进制打开:


file:///C:/DOCUME~1/ADMINI~1/LOCALS~1/Temp/msohtml1/01/clip_image040.jpg
可以看到,"中国"两个中文确实总共占用6个字节,可是为什么printf输出后计算的总字节数不是9而是7呢?
A: 当然,源代码的编码格式以及源代码中字符串的编码格式和最终输出的编码格式不一定是一样的。正如前面所说,printf
函数的返回值以终端真实输出的字节数为准,终端的字符编码是GBK,不管原来编写代码的对应编码是什么,和最终输出并没
有必然关系,所以结果依然是7.

Q:可执行文件hello.exe中的"abc中国"字符串对应的编码是什么呢?
A: 将hello.exe用十六进制打开,


file:///C:/DOCUME~1/ADMINI~1/LOCALS~1/Temp/msohtml1/01/clip_image042.jpg
可以发现,"中国"字符串用的是用GBK编码格式保存的;这里,应该可以理解为什么一直输出7了吧。

Q: 编码这个东西还真有意思,如何改变cmd终端的编码格式?
A: 这个编码格式也被称为代码页,可以使用chcp命令:
直接输入chcp可以得到当前的代码页:


file:///C:/DOCUME~1/ADMINI~1/LOCALS~1/Temp/msohtml1/01/clip_image044.jpg
更改为utf-8格式,使用chcp 65001 :


file:///C:/DOCUME~1/ADMINI~1/LOCALS~1/Temp/msohtml1/01/clip_image046.jpg
此时再运行hello.exe,结果依然是7,但是"中国"字符变成了乱码。


file:///C:/DOCUME~1/ADMINI~1/LOCALS~1/Temp/msohtml1/01/clip_image048.jpg
我想原因你应该知道了。

Q:输出字符串,不也是可以用unicode宽字符的吗?
A: 是的。
如下代码:
1.    #include   
2.      
3.    int main()  
4.    {  
5.        int ret;  
6.        ret = wprintf(L"abc%S", L"中国");  
7.        printf(" ret is %d\n", ret);  
8.      
9.        return 0;  
10.  }  


[Windows-VS2010]输出:


file:///C:/DOCUME~1/ADMINI~1/LOCALS~1/Temp/msohtml1/01/clip_image050.jpg
在Mac或者Ubuntu下,此代码暂未测试;因为关于wprintf以及%ls和%S,不同系统下表现不一致,可能需要修改代码。

Q: printf函数里面的输出格式有好多种,但是和对应输出格式的数据如果不一致,会导致什么问题呢?
A: 当然是可能会发生一些问题了,因为printf函数是动态解析格式字符串,然后将对应的数据来填充,可能出现本来是
%d格式,但是却用了一个double的数据来填充,这样就可能导致错误。

Q: 比如:

1.    #include   
2.      
3.    int main()  
4.    {  
5.        printf("%d", 1.5);  
6.      
7.        return 0;  
8.    }  

为什么编译阶段没有出现编译的错误呢?
A: 因为编译器将"%d"当成了一个字符串,且并不去分析其中的格式,这个交给了printf函数的实现内部。其实,也就是把判断是否正确的责任交给了程序员,
如果程序员弄错了,那么结果就可能会跟着错。

Q: printf函数解析格式输出的代码该怎么写?
A: 这个需要根据字符串中的%为标志,出现这个,说明后面可能跟着一个对应格式的数据,比如d,那么说明是个整数,将栈中对应的数据来填充;如果是s,
那就是取字符串来填充,依次类推;如果没有遇到%符号,那么按原样输出。
一个简单且调用了printf的类printf函数:
1.    int __cdecl cc_printf(  
2.        const char *format,  
3.        ...  
4.        )  
5.    {  
6.        va_list argulist;  
7.        int ret = 0;  
8.      
9.        va_start(argulist, format);  
10.   
11.      while (*format)  
12.      {  
13.      if(*format != '%')  
14.      {  
15.          putchar(*format);  
16.          ++ret;  
17.          goto loop;  
18.      }  
19.      else  
20.      {  
21.          ++format;  
22.          switch (*format)  
23.          {  
24.          case 'c':  
25.          {  
26.              int value = va_arg(argulist, int);  
27.              ret += printf("%c", (char)value);  
28.              goto loop;  
29.          }  
30.   
31.          case 's':  
32.          {  
33.              char *value = va_arg(argulist, char *);  
34.              ret += printf("%s", value);  
35.              goto loop;  
36.          }  
37.   
38.          case 'd':  
39.          {  
40.              int value = va_arg(argulist, int);  
41.              ret += printf("%d", value);  
42.              goto loop;  
43.          }  
44.   
45.          case 'o':  
46.          {  
47.              int value = va_arg(argulist, int);  
48.              ret += printf("%x", value);  
49.              goto loop;  
50.          }  
51.   
52.          case 'x':  
53.          {  
54.              int value = va_arg(argulist, int);  
55.              ret += printf("%x", value);  
56.              goto loop;  
57.          }  
58.   
59.          case 'X':  
60.          {  
61.              int value = va_arg(argulist, int);  
62.              ret += printf("%X", value);  
63.              goto loop;  
64.          }  
65.   
66.          case 'u':  
67.          {  
68.              unsigned value = va_arg(argulist, unsigned);  
69.              ret += printf("%u", value);  
70.              goto loop;  
71.          }  
72.   
73.          case 'f':  
74.          {  
75.              double value = va_arg(argulist, double);  
76.              ret += printf("%f", value);  
77.              goto loop;  
78.          }  
79.   
80.          default:  
81.          {  
82.              goto loop;  
83.          }  
84.          }  
85.      }  
86.   
87.  loop:  
88.      ++format;  
89.      }  
90.   
91.      va_end(argulist);  
92.   
93.      return ret;  
94.  }  


Q: 还回到刚刚那个问题吧。
1.    printf("%d", 1.5);  

这个会输出什么呢?

A: 编译一下,输出:


file:///C:/DOCUME~1/ADMINI~1/LOCALS~1/Temp/msohtml1/01/clip_image052.jpg

Q: 为什么会输出这个神奇的数字呢?
A: 根据IEEE754的标准,双精度浮点数1.5的二进制表示形式是:
00 00 00 00 00 00 F8 3F
可以看到,低4个字节都是0,而%d正好只取了低4个字节,所以结果是0.

为了方便地打印出double数据中各个字节的值,可以使用如下的union结构:
1.    union double_data  
2.    {  
3.        double f;  
4.        unsigned char data[sizeof(double) / sizeof(char)];  
5.    };  


Q: 有的时候,printf格式串后的参数个数超过了格式串中的对应格式,会是什么结果?
A: 先写个代码:
1.    #include   
2.      
3.      
4.    int main()  
5.    {  
6.        printf("%d %d", 1, 2, 3);      
7.      
8.        return 0;  
9.    }  

输出的结果是什么呢?


file:///C:/DOCUME~1/ADMINI~1/LOCALS~1/Temp/msohtml1/01/clip_image054.jpg

Q: 为什么不是输出2和3呢,或者输出3和2呢?
A: 看汇编:
使用cl  /Fa hello.c命令编译,得到hello.asm文件:
1.    ; Listing generated by Microsoft (R) Optimizing Compiler Version 16.00.30319.01   
2.      
3.        TITLE   F:\c_codes\hello.c  
4.        .686P  
5.        .XMM  
6.        include listing.inc  
7.        .model  flat  
8.      
9.    INCLUDELIB LIBCMT  
10.  INCLUDELIB OLDNAMES  
11.   
12.  _DATA   SEGMENT  
13.  $SG2637 DB  '%d %d', 00H  
14.  _DATA   ENDS  
15.  PUBLIC  _main  
16.  EXTRN   _printf:PROC  
17.  ; Function compile flags: /Odtp  
18.  _TEXT   SEGMENT  
19.  _main   PROC  
20.  ; File f:\c_codes\hello.c  
21.  ; Line 5  
22.      push    ebp  
23.      mov ebp, esp  
24.  ; Line 6  
25.      push    3  
26.      push    2  
27.      push    1  
28.      push    OFFSET $SG2637  
29.      call    _printf  
30.      add esp, 16                 ; 00000010H  
31.  ; Line 8  
32.      xor eax, eax  
33.  ; Line 9  
34.      pop ebp  
35.      ret 0  
36.  _main   ENDP  
37.  _TEXT   ENDS  
38.  END  


可以看到;  Line 6标识的地方,有4个参数压栈的操作,依次是3, 2, 1和$SG2637, $SG2637也就是对应"%d %d"这个字符串。
这个时候,你应该看明白了吧,调用printf("%d%d", 1, 2, 3);函数的时候,此函数有4个参数,但是从右向左依次压栈,最后解析
"%d %d"字符串的时候,第一个格式就对应栈中下一个元素,也就是1, 依次找到第二个对应格式的数据,也就是2.所以最后输出
了1和2.

Q: 如果printf参数中第一个参数中的数据格式数多于后面的参数,那又会发生什么呢?
A: 当然按照之前的原理,解析数据格式的时候会从栈里多解析一个数据,这很可能导致之后的运行错误,因为它很可能并不是
程序员的意图。

Q: 看到一些地方,printf函数后面的参数有好几个带有自增或者自减的操作,最后的结果真的很神奇,这有什么规律吗?
A: 你是说,比如
1.    #include   
2.      
3.      
4.    int main()  
5.    {  
6.        int i = 1;  
7.        printf("%d %d", ++i, ++i);      
8.      
9.        return 0;  
10.  }  


最后的结果很可能不是你想要的,不过这个代码也可能让别人有好几种推测。所以,这种代码最好是不要写,要么写就写清楚的,大家
都能明白的代码;同时,这种代码也不是能够很方便移植的,所以还是尽量少写这样的代码。

Q: 听说,printf函数是带缓冲的输出?这个和不带缓冲的输出有什么区别?
A: 正如一个道理,cpu速度很快,外设运行速度很慢,为了避免这种速度差距导致cpu的效率被严重降低,所以,对于外设的操作,很多都有缓冲,包括显示器、键盘、硬盘等等。不带缓冲的也就是如果需要输出,那么立即输出,不会被缓冲系统来处理。

Q: 我怎么感觉不到是不是缓冲输出的?
A: 一个很容易感受到缓冲的是在cmd里面提示输入的地方。你可以输入一段数据,然后按回车,系统会进行对应处理。这个地方表示的是缓冲输入。
对于缓冲输出,举个例子:
1.    #include   
2.    #include   
3.      
4.    int main()  
5.    {  
6.        int i = 1;  
7.        char *buf = (char *)malloc(512);  
8.        if(buf)  
9.        {  
10.      setbuf(stdout, buf);  
11.      }  
12.   
13.      printf("hello");  
14.      printf("xichen");  
15.   
16.      return 0;  
17.  }  


这里,申请了一个512字节的空间,然后使用setbuf函数将标准输出的缓冲区设置为buf.
然后向标准输出输出2个字符串。
在printf("xichen");行打断点,调试到此行,可以发现控制台什么也没输出,继续运行依然没有输出,没有输出的原因就在于它们被缓冲了。
然后,修改代码,设置缓冲区为空:
1.    #include   
2.    #include   
3.      
4.    int main()  
5.    {  
6.        int i = 1;  
7.      
8.        setbuf(stdout, NULL);  
9.      
10.      printf("hello");  
11.      printf("xichen");  
12.   
13.      return 0;  
14.  }  


依然在printf("xichen");行打断点,调试:


file:///C:/DOCUME~1/ADMINI~1/LOCALS~1/Temp/msohtml1/01/clip_image056.jpg
运行到断点处,此时查看控制台输出:


file:///C:/DOCUME~1/ADMINI~1/LOCALS~1/Temp/msohtml1/01/clip_image058.jpg
可以看出,前面一句代码的字符串已经被输出了,这就是没有缓冲的。



结束语
    前面写了这么多,不知不觉发现printf这个基本输出的函数确实不简单;但是了解了关于它的代码的底层信息,也会对它的理解更深刻,
前面写的东西不一定只适用于printf, 很多内部实现的实验都可以从上面的内容找到一些影子,希望这些对大家有帮助。


[ 本帖最后由 tiankai001 于 2012-12-15 07:19 编辑 ]
此帖出自单片机论坛
 
 

回复

6366

帖子

4929

TA的资源

版主

板凳
 
Q: 前面是关于输出的,给个关于输入的代码吧。
A: 如下:
1.    #include   
2.      
3.    int main()  
4.    {  
5.        int i;  
6.        scanf("%d", &i);   
7.        printf("%d", i);   
8.        return 0;  
9.    }  

Q:%d格式和printf中的都表示整形是吧,为什么后面用了&i,用i的地址呢?
不能直接用下面的代码吗?
1.    scanf("%d", i);  


A: 这是因为,scanf是一个函数调用,如果仅仅将i以值的形式传进去,那么scanf函数最终不会也没有能力修改对于它来说是外部的i.
总之,原因在于c语言函数调用参数的值传递原理。
对于char *字符串,也同样是这样的原理。
1.    char buf[32];  
2.    scanf("%s", buf);  



你会发现这里的buf也是地址。
Q:说说这个代码的运行效果吧。
A: 暂且把开头那个代码保存为scanf.c.然后简单编译一下:
[Mac-10.7.1 Lion  Intel-based]
gcc  scanf.c
file:///C:/DOCUME~1/ADMINI~1/LOCALS~1/Temp/msohtml1/01/clip_image002.jpg
(因为笔者习惯在苹果系统下面编程,可以看到上面的提示Mac-10.7.1表示在mac系统10.7.1下面;
当然,因为某些场合可能需要切换到windows或者ubuntu,都会具体显示各个平台,以保证读者测试代码的正确性。)
gcc版本是:
file:///C:/DOCUME~1/ADMINI~1/LOCALS~1/Temp/msohtml1/01/clip_image004.jpg
Q: 为什么gcc scanf.c执行后什么都没输出?
A: gcc编译源代码,如果什么都没输出,那么说明已经正确编译成功了。
可以看到,使用ls  -l | grep a.out 命令可以看到同目录下面已经有了a.out这个文件,这也是用上面的编译命令默认编译得到的可执行文件名。
file:///C:/DOCUME~1/ADMINI~1/LOCALS~1/Temp/msohtml1/01/clip_image006.jpg
Q: 看看运行效果吧。
A:  在命令行下输入./a.out ,结果如下:
file:///C:/DOCUME~1/ADMINI~1/LOCALS~1/Temp/msohtml1/01/clip_image008.jpg
可以看到,输入完./a.out后回车,命令行进入了等待状态,输入100后回车,输出了100
Q: 怎么输出的100和它后面的命令行提示符连到一起了?
A: 因为这个程序的输出的最后没有换行导致的。
修改代码:
1.    #include   
2.      
3.    int main()  
4.    {  
5.        int i;  
6.        scanf("%d", &i);  
7.        printf("%d\n", i);  
8.        return 0;  
9.    }  


重新编译,再运行:
file:///C:/DOCUME~1/ADMINI~1/LOCALS~1/Temp/msohtml1/01/clip_image010.jpg
可以看到,这次运行的输出和后面的命令行提示符已经分开了。如果查看此解释器bash的源代码,也可以看到,每次执行完应用程序,
解释器就会接着将自己的提示符再次输出,所以,如果让程序的执行结果不和它混在一起,一般都需要在代码加换行。
如果需要查看bash源代码,下面是苹果的官方开源代码网址:
Q: scanf函数有返回值吗?
A: 是的。如下是scanf函数的原型:
[Mac-10.7.1 Lion Intel-based]
1.    int  scanf(const char * __restrict, ...) __DARWIN_LDBL_COMPAT(scanf) __scanflike(1, 2);  



Q: 原型中__restrict是什么意思?
A: 它是C99标准引入的,它只可以用于限定指针,并表明该指针是访问一个数据对象的唯一且初始的方式,这样编译器就可以做一些特定的优化。
举个例子吧:
1.    int f(int * x, int * y)  
2.    {  
3.        *x = 0;  
4.        *y = 1;  
5.        return *x;  
6.    }  


上面这段代码,看起来似乎只返回了和x习惯的数据,但是不能肯定x和y究竟有没有关系,因为可能y和x是一样的;
所以,编译器不敢直接将代码编译优化成:
1.    int f(int * x, int * y)  
2.    {  
3.        *x = 0;  
4.        *y = 1;  
5.        return 0;  
6.    }  


但是,如果加上__restrict关键字:
1.    int f_restrict(int *__restrict x, int *__restrict y)  
2.    {  
3.        *x = 0;  
4.        *y = 1;  
5.        return *x;  
6.    }  


上面的代码表示:对x对应内容的修改只可能通过x,绝对不可能和y相关,所以编译器就可以做上面的优化了。
scanf函数原型,还有后面的__DARWIN_LDBL_COMPAT(scanf) __scanflike(1, 2)的意思,可以自己去查看,这里不介绍了。
Q: scanf函数返回值是什么意思?
A: 可以使用man scanf命令得到scanf函数的说明。
file:///C:/DOCUME~1/ADMINI~1/LOCALS~1/Temp/msohtml1/01/clip_image012.gif
可以看到,它返回的是被成功赋值变量的个数。
也就是说,上面的代码scanf("%d",&i); 如果返回为1,则表示成功输入了变量i.
修改代码来验证:
1.    #include   
2.      
3.    int main()  
4.    {  
5.        int i;  
6.        int ret;  
7.        ret = scanf("%d", &i);  
8.        if(ret == 1)  
9.            printf("%d\n", i);  
10.      else  
11.          printf("input i error\n");  
12.      return 0;  
13.  }  


编译运行:
输入100:
file:///C:/DOCUME~1/ADMINI~1/LOCALS~1/Temp/msohtml1/01/clip_image014.gif
重新运行,输入字符d
file:///C:/DOCUME~1/ADMINI~1/LOCALS~1/Temp/msohtml1/01/clip_image016.gif
可以发现这里就进入了错误显示。
scanf函数的返回值能够很好地帮助我们分析需要输入的变量到底有没有被正确输入。
Q: 如果调用scanf函数进行输入,但是输入了不正确格式的数据,再次循环输入,怎么不能输入了,且一直打印错误输入?
代码是:
1.    #include   
2.      
3.    int main()  
4.    {  
5.        int i;  
6.        int ret;  
7.        while(1)  
8.        {  
9.            ret = scanf("%d", &i);  
10.          if(ret == 1)  
11.              printf("%d\n", i);  
12.          else  
13.              printf("input i error\n");  
14.      }  
15.      return 0;  
16.  }  


运行时输入一个字符d :
file:///C:/DOCUME~1/ADMINI~1/LOCALS~1/Temp/msohtml1/01/clip_image018.gif
会一直打印input i error不能停止,这是为什么?
A: 这个原因就在于输入缓冲区了。还记得前面说过的缓冲区的概念吗?提到了输出缓冲区,scanf同样也有一个输入缓冲区。
调用scanf, scanf就会从它的输入缓冲区中来获取对应的数据,如果获取到就会传入再继续执行,否则就会等待输入。
当输入了字符d并回车后,scanf会将d和整形匹配,结果发现是不匹配的,所以会打印input i error信息,接着再次进入循环,
但是之前的输入缓冲区中依然保留着字符d呢,所以又会继续打印错误信息,依次循环永不停止。
Q: 那应该怎么办呢?
A: 很简单,如果发现输入数据错误,那么清空输入缓冲区即可。使用rewind(stdin);即可,
修改后的代码为:
1.    #include   
2.      
3.    int main()  
4.    {  
5.        int i;  
6.        int ret;  
7.        while(1)  
8.        {  
9.            ret = scanf("%d", &i);  
10.          if(ret == 1)  
11.              printf("%d\n", i);  
12.          else  
13.          {  
14.              rewind(stdin);  
15.              printf("input i error\n");  
16.          }  
17.      }  
18.      return 0;  
19.  }  

运行:

file:///C:/DOCUME~1/ADMINI~1/LOCALS~1/Temp/msohtml1/01/clip_image020.gif
Q: 可以使用fflush(stdin); 来清空输入缓冲区吗?
A: c语言标准并没有对此规定可行,在笔者的mac系统下此函数就无效;所以,最好不要用这个。
不过,因为不同系统都可能存在差异,就算上面用的rewind(stdin); 也许在某些系统上也无效;所以,有个
更好的建议,那就是使用如下代码:
1.    int ch;  
2.    while((ch = getchar()) != EOF && ch != '\n')  
3.        ;  



Q: 我写了如下代码,scanf用于输入两个整形数,为什么总是有问题?
1.    #include   
2.      
3.    int main()  
4.    {  
5.        int i, j;  
6.        int ret;  
7.        ret = scanf("%d,%d", &i, &j);  
8.        if(ret == 2)  
9.            printf("%d %d\n", i, j);  
10.      else  
11.          printf("input i or j error\n");  
12.      return 0;  
13.  }  


运行:
file:///C:/DOCUME~1/ADMINI~1/LOCALS~1/Temp/msohtml1/01/clip_image022.gif
A: 你需要注意scanf的输入格式,"%d,%d",两个整形中间是有个逗号的,但是看看你的输入,中间是个空格,并没有
逗号,所以会导致错误。所谓,格式化输入,你得符合它的格式才行。
file:///C:/DOCUME~1/ADMINI~1/LOCALS~1/Temp/msohtml1/01/clip_image024.gif
和这个问题类似的还有:
scanf("%d\n", &i);          格式串中有\n,所以需要输入两次回车才能继续;
scanf("%2d%2d", &i, &j);    %2d指整形占用两位,所以当输入1234时, 12被赋值给i, 34被赋值给j;
float f;  scanf("%4.1f", f);      scanf的格式串中没有类似小数位数的格式,所以这样的输入经常会导致输入错误;
等等。
Q: 我想输入一个double型浮点数,怎么会不管怎么输入都得不到正确的结果?
1.    #include   
2.      
3.    int main()  
4.    {  
5.        double d;  
6.        scanf("%f", &d);  
7.        printf("%f\n", d);  
8.        return 0;  
9.    }  


运行:
file:///C:/DOCUME~1/ADMINI~1/LOCALS~1/Temp/msohtml1/01/clip_image026.gif
A: 这个问题在于, scanf格式中的%f表示输入的是一个float类型,在笔者的平台上是4字节的,而实际上却被保存
在double类型变量中,所以它被保存到变量d的低4个字节中(intel小端),但是整个double变量的值却和低4个字节的
值根本不能直接运算,可以参考IEEE 754关于浮点数的标准。
其实,在编译过程中应该也会有警告:
file:///C:/DOCUME~1/ADMINI~1/LOCALS~1/Temp/msohtml1/01/clip_image028.gif
解决这个问题不复杂,只需要将格式%f改为%lf即可。
Q: 写了一个代码,scanf用于输入字符,但是第二个需要输入的字符却使用不正确,为什么呢?
1.    #include   
2.      
3.    int main()  
4.    {  
5.        char ch1, ch2;  
6.        scanf("%c%c", &ch1, &ch2);  
7.        printf("|%c|%c|\n", ch1, ch2);  
8.        return 0;  
9.    }  


运行:
输入a然后回车准备输入第二个字符,但是没有进入等待输入状态,却直接输出了2个字符,且输出的不正确。
file:///C:/DOCUME~1/ADMINI~1/LOCALS~1/Temp/msohtml1/01/clip_image030.gif
A: 其实这里,主要是因为在输入a后的回车也被当做了一个字符用于存储在b当中导致的。记得,scanf在输入整形、浮点型等
数据的时候可以忽略回车、空格、制表符等字符,但是输入格式为%c的时候就不这样了,此时它会很认真。
所以,将输入的代码改为scanf("%c\n%c",&ch1, &ch2); 即可满足你的要求。
Q: scanf函数和printf函数一样都有输入或输出的变量个数和格式中的变量个数不一致的情况吧,那么运行结果会如何呢?
A: 是的,当出现这样的不一致的时候,可能导致运行结果匪夷所思。当然,可以根据分析scanf的源代码得到不同情况下的运行结果。
比如,scanf("%d",&i, &j);   这种情况下, j不会被输入,因为格式中仅有1个,只会向i中输入,&j仅仅被当做一个参数传进来,却没有使用。
再比如,scanf("%d%d", &i);  这种情况下,多余需要被输入的整数可能导致某个内存被意外改写甚至崩溃;
当然,不管怎么样,上面这样的写法终究可能导致一些问题,不过有一点是肯定的,一个优秀的编译器会给出相应的警告信息,这样的代码
不要写在项目中,把它当做研究c语言内部机制的一种方式即可。
Q: 这个scanf函数还真有意思,到底还有多少有意思的?
A: 其实,对于输入概念来说,到底用什么格式来输入是五花八门的,在特定需求下可能产生一种别于寻常的输入方式,可能由此产生了一种
新的输入格式%XXXX.
举些例子:
类似%*d的方式表示此输入的整数被忽略;
scanf("%d%*d%d", &i, &j);           输入:  100 2 200 然后回车,100被保存在i中,2被忽略, 200被保存在j中;
char s[32];  scanf("%[abcd]", s);    %[abcd]表示如果输入的字符是a,b,c,d中的任何一个,那么接收,否则不接收;
如果输入  abr3dc 然后回车,那么字符串ab将被保存在s中。 这里可以看到正则表达式的影子。
查看c标准,可以发现%[abcd]也可以用%[a-d]来表示。
使用%i可以输入整数,但是可以输入八进制或者十六进制的形式,这是它和%d作为输入整数格式的不同之处:
scanf("%i", &i);   输入012然后回车,10会被保存在i中; 如果输入0x12, 那么18会被保存在i中;
Q: 看来scanf还是很精彩的,不过要是想全面学习它的格式规则,还真是不容易。
A: 是的,c语言很底层,越是底层的语言,越是深入细节就越需要精深的知识去解释,犯错误不是c语言的错,
而是程序员的错。
xichen
2012-5-10 12:13:34

[ 本帖最后由 tiankai001 于 2012-12-15 07:29 编辑 ]
此帖出自单片机论坛
 
 
 

回复

6366

帖子

4929

TA的资源

版主

4
 
开头将文章中代码所在的环境介绍下:
[Mac-10.7.1 Lion Intel-based]
Q: 看到stdio.h中有这么多关于输入或者输出的函数,怎么会这么多?
A: 其实基本的函数不多,不过从易于上层使用的角度,又被封装出很多特定情况的函数接口,所以才显得很多。
Q: 比如关于字符操作的函数有getc, fgetc, getch, getche还有getchar,它们有什么区别吗?
A: 一个重要的区别是getc, fgetc, getchar是标准函数,而getch和getche不是标准规定的。不是标准,就意味着它的实现可能在不同平台有不一样的表现。正如getch一样,windows平台被包含在conio.h头文件中,运行到getch,可能需要等待输入,当有输入时就会运行过去不需要再按回车; getche在windows下和getch类似,不过多了最后的字母e,它表示echo,即在输入后会回显输入的字符。不过它们在mac平台的表现却不大一样,这里不做具体介绍。
现在介绍下getc, fgetc和getchar.用man getc得到如下信息:
file:///C:/DOCUME~1/ADMINI~1/LOCALS~1/Temp/msohtml1/01/clip_image002.gif
可以发现,getc基本等同于fgetc,getchar是getc的特殊形式,是从标准输入获取字符。
Q: getch和getche接收了输入即可继续运行,而getchar函数当缓冲区中没有数据时,需要输入字符最终回车才能继续执行,这是不是意味着getch和getche是不带缓冲的,而getc, fgetc和getchar是带缓冲的?
A: 是的。按照标准来说,getc和fgetc在不涉及交互的时候是全缓冲模式(在缓冲区大小满后提交数据),如果是涉及到交互(如标准输入),那么是行缓冲;而getchar就行缓冲模式(回车会提交输入缓冲区数据,不过如果缓冲区已经有数据了就不需要等待回车了).
Q: 既然fgetc是接收输入的字符,返回值用char或者unsigned char不就行了,为什么用int呢?
A: 这个主要是因为文件结束或者读写文件出错的标志被规定成EOF,也就是-1导致的。unsigned char根本取不到-1这个值,而如果用char做返回值的话,它无法分辨0xFF字符和EOF,因为这两个数值都被char认为是-1,所以它也不能作为返回值。
举个例子吧:
使用如下代码向一个文件中只写入0xFF字符,然后保存:
1.    #include   
2.      
3.    int main (int argc, const char * argv[])  
4.    {   
5.        FILE *fp = fopen("test", "w");  
6.        if(fp)  
7.        {  
8.            fputc(0xFF, fp);  
9.            fclose(fp);  
10.      }  
11.        
12.      return 0;  
13.  }  


运行完,用hexdump确认此文件中的数据为0xFF:
file:///C:/DOCUME~1/ADMINI~1/LOCALS~1/Temp/msohtml1/01/clip_image004.gif
然后使用如下代码读取test文件中的数据,并打印出来:
1.    #include   
2.      
3.    int main (int argc, const char * argv[])  
4.    {   
5.        FILE *fp = fopen("test", "r");  
6.        if(fp)  
7.        {  
8.            char ch;  
9.      
10.          printf("open test ok\n");  
11.          while((ch = fgetc(fp)) != EOF)  
12.          {  
13.              printf("%d", ch);  
14.          }  
15.   
16.          printf("read test end\n");  
17.          fclose(fp);  
18.      }  
19.        
20.      return 0;  
21.  }  


运行后可以发现输出结果如下,这就意味着fgetc执行是遇到0xFF时被当做了EOF而导致结束了。
file:///C:/DOCUME~1/ADMINI~1/LOCALS~1/Temp/msohtml1/01/clip_image006.gif
Q: 上面提到getchar等同于getc(stdin), stdin到底是什么?
A: [Mac-10.7.1 Lion Intel-based]
stdio头文件中:
1.    #define stdin   __stdinp  
2.    #define stdout  __stdoutp  
3.    #define stderr  __stderrp  

1.    extern FILE *__stdinp;  
2.    extern FILE *__stdoutp;  
3.    extern FILE *__stderrp;  


可以看到,它们只是FILE *类型的一个变量,对于任何一个应用程序,使用c运行时库都会默认自动为应用程序创建这3个文件句柄。
Q: 还有关于字符串输入的gets函数,听说使用它进行输入有风险的,如何表现的?
A: 因为它没有对于输入数据大小进行确定,也就是可能导致输入的数据过多覆盖了不该覆盖的数据导致出问题。
举个例子:
1.    #include   
2.      
3.    #define PRINT_CH(ch)    printf(#ch" is %c\n", (ch));  
4.    #define PRINT_STR(str)  printf(#str" is %s\n", (str));  
5.      
6.    int main (int argc, const char * argv[])  
7.    {   
8.        char ch = 'a';  
9.        char buf[4];  
10.      char *ret = gets(buf);  
11.      if(ret != NULL)  
12.      {  
13.          PRINT_STR(ret)  
14.      }  
15.      PRINT_CH(ch)  
16.        
17.      return 0;  
18.  }  


运行:
如果输入3个字符hel后回车,得到的结果是正常的:
file:///C:/DOCUME~1/ADMINI~1/LOCALS~1/Temp/msohtml1/01/clip_image008.gif
如果输入了4个字符hell后回车,结果如下:
file:///C:/DOCUME~1/ADMINI~1/LOCALS~1/Temp/msohtml1/01/clip_image010.gif
可以看到ch字符的数据已经不正常了;
如果输入5个字符hello后回车,结果如下:
file:///C:/DOCUME~1/ADMINI~1/LOCALS~1/Temp/msohtml1/01/clip_image012.gif
可以看到ch字符已经被覆盖为hello中的o了。这和刚刚输入4个字符的方式都是发生了缓冲区溢出,也就是超过了缓冲区buf的大小,覆盖了其它数据。
至于ch为什么是字符o, 这需要明白:ch和buf都被保存在栈中,且ch和buf相邻,如果栈是从高到低,那么ch保存在高地址,数据hello将前4个字节保存在buf中,最后一个o就被保存在了ch所在的地址区域里面。
Q: 既然这样,那么使用什么可以更好地避免缓冲区溢出呢?
A: 可以使用fgets函数,它的一个参数就是缓冲区大小。
1.    char *fgets(char * restrict str, int size, FILE * restrict stream);  


如果读取成功,函数返回读取字符串的指针;
如果开始读取直接遇到文件结尾,返回NULL,缓冲区数据和原来保持一致;
如果读取出错,返回NULL, 缓冲区数据不确定;
Q: fgets如果返回NULL,可能是直接遇到文件结束或者获取出错,怎么区分呢?
A: 这就需要如下两个函数了:feof和ferror.
1.    int  feof(FILE *stream);  

1.    int  ferror(FILE *stream);  


如果遇到文件尾,那么feof返回非0的数值,否则返回0.
如果遇到文件操作错误,那么ferror返回非0的数值,否则返回0.
Q: 做一个测试吧,从一个文件中不断读取数据,然后打印出来。
A: 首先,先写个脚本,向一个文件中写入100个字符,为依次重复10次0123456789.
1.    #!/bin/bash  
2.      
3.    i=0  
4.    j=0  
5.      
6.    while [ $i -lt 100 ];  
7.    do  
8.        let "j = i % 10"  
9.        echo -n $j >> numbers  
10.      let "i = i + 1"  
11.  done  


执行它,会在同目录下找到一个numbers的文件,使用vi打开确认一下内容:
file:///C:/DOCUME~1/ADMINI~1/LOCALS~1/Temp/msohtml1/01/clip_image014.gif
现在使用fgets函数将此文件的数据读出来:
1.    #include   
2.    #include   
3.      
4.    #define PRINT_CH(ch)    printf(#ch" is %c\n", (ch));  
5.    #define PRINT_STR(str)  printf(#str" is %s\n", (str));  
6.      
7.    int main (int argc, const char * argv[])  
8.    {   
9.        FILE *fp = fopen("numbers", "r");  
10.      if(fp)  
11.      {  
12.          char buf[11] = {0};  
13.          while(fgets(buf, sizeof(buf), fp) != NULL)  
14.          {  
15.              if(!ferror(fp))  
16.                  PRINT_STR(buf)  
17.              else  
18.                  PRINT_STR("ferror happens...")  
19.               
20.              memset(buf, 0, sizeof(buf));  
21.          }  
22.          fclose(fp);  
23.      }  
24.   
25.      return 0;  
26.  }  


这里,为了分便显示结果,缓冲区被设置大小为11,最后一个字节保存结束符\0, 前10个字符保存0~9.
运行结果:
file:///C:/DOCUME~1/ADMINI~1/LOCALS~1/Temp/msohtml1/01/clip_image016.gif
Q: fgets会自动在缓冲区结尾加上\0作为结束符是吧?
A: 是的。这和strcpy在这个方面是一致的。不过,fgets也有自己的特点,正如gets一样,遇到回车符,它会提前结束;如果遇到回车符,而且缓冲区还没填满,那么回车符会被填充到缓冲区中,最后再加上\0作为结束符。
看个例子:
1.    #include   
2.    #include   
3.      
4.    #define PRINT_D(intValue)   printf(#intValue" is %lu\n", (intValue));  
5.    #define PRINT_CH(ch)        printf(#ch" is %c\n", (ch));  
6.    #define PRINT_STR(str)      printf(#str" is %s\n", (str));  
7.      
8.    int main (int argc, const char * argv[])  
9.    {   
10.      char buf[3];  
11.      char *ret = fgets(buf, sizeof(buf), stdin);  
12.      if(ret)  
13.      {  
14.          PRINT_D(strlen(buf))  
15.          PRINT_STR(buf)  
16.      }  
17.        
18.      return 0;  
19.  }  


运行:
输入h然后回车, 结果:
file:///C:/DOCUME~1/ADMINI~1/LOCALS~1/Temp/msohtml1/01/clip_image018.gif
可以发现回车字符被保存在了buf中.它和gets在这方面不一致,这是需要注意的。
Q: 如果不希望到换行就结束,也可以指定缓冲区大小,该怎么办?
A: 可以使用fread(不过它是适用于二进制读的函数,用于文本方式可能在某些情况下出错,需要小心).
1.    size_t  fread(void *restrict ptr, size_t size, size_t nitems, FILE *restrict stream);  


ptr: 表示读入数据保存的地址;
size: 表示每组数据的大小;
nitems: 表示读入数据的组数;
stream: 表示文件指针;
返回值: 成功读取数据的组数;如果返回NULL,可能是直接遇到文件尾或者读取出错,需要用feof和ferror来区分;当然,如果读取不成功,返回值会比传入的参数nitems要小。
这个函数使用的时候要注意:正因为它是用于二进制读的,它不会在读取ok后自动为缓冲区最后加上\0作为结束符,这个要小心;如果第二个和第三个参数互换了,那么返回值也会发生相应改变,这也需要小心。
举个例子吧:
1.    #include   
2.    #include   
3.      
4.    #define PRINT_D(intValue)   printf(#intValue" is %lu\n", (intValue));  
5.    #define PRINT_CH(ch)        printf(#ch" is %c\n", (ch));  
6.    #define PRINT_STR(str)      printf(#str" is %s\n", (str));  
7.      
8.    int main (int argc, const char * argv[])  
9.    {   
10.      char buf[3] = {0};  
11.      size_t ret = fread(buf, sizeof(buf) - 1, 1, stdin);  
12.      if(ret == 1)  
13.      {  
14.          PRINT_STR(buf)  
15.      }  
16.        
17.      return 0;  
18.  }  


可以看到,开始就将buf初始化为0了,fread的第二个参数是buf总大小减去1的数值,因为后面把读取的数据当成char *字符串,最后一个字节需要保存为\0.
第三个参数为1表示1组数组,第二个参数表示每组数据大小为sizeof(buf) - 1. 最后一个参数表示从标准输入stdin读取。
需要注意:正如之前说过,文件读写为交互设备时,是属于行缓冲,需要输入回车来提交缓冲区。
运行:
file:///C:/DOCUME~1/ADMINI~1/LOCALS~1/Temp/msohtml1/01/clip_image020.gif
输入he并回车,可以看到打印了he.
如果输入h并回车,那么效果如下:
file:///C:/DOCUME~1/ADMINI~1/LOCALS~1/Temp/msohtml1/01/clip_image022.gif
可以发现回车字符被保存在了buf中。
在这里不用太小心,如果输入多余2个字符,不会发生缓冲区溢出,因为fread有参数已经标志了缓冲区大小。
Q: 上面是关于文件输入,文件输出的fputc, putc, putchar是不是也和fgetc, getc, getchar的关系类似?
A: 是的,使用man putc可以看到它们之间的关系。
file:///C:/DOCUME~1/ADMINI~1/LOCALS~1/Temp/msohtml1/01/clip_image024.gif
Q: 标准中也没有putch和putche么?
A: 是的,实际上在mac系统默认下也没发现它们的非标准实现。
Q: 字符串输出的fputs和puts有什么区别么?
A:
file:///C:/DOCUME~1/ADMINI~1/LOCALS~1/Temp/msohtml1/01/clip_image026.gif
可以看到,fputs将字符串向指定文件输出,但是并不会自动加上换行字符;而puts会自动加上换行字符。
另外,fputs返回EOF表示操作失败,返回非负数表示成功;所以判断fputs是否成功最好使用if(fputs(xxxx) != EOF)或者if(fputs(xxx) == EOF)来表示。
如下代码:
1.    #include   
2.    #include   
3.      
4.    #define PRINT_D(intValue)   printf(#intValue" is %lu\n", (intValue));  
5.    #define PRINT_CH(ch)        printf(#ch" is %c\n", (ch));  
6.    #define PRINT_STR(str)      printf(#str" is %s\n", (str));  
7.      
8.    int main (int argc, const char * argv[])  
9.    {   
10.      int ret = fputs("hello", stdout);  
11.      if(ret != EOF)  
12.      {  
13.          PRINT_D(ret)  
14.      }  
15.        
16.      return 0;  
17.  }  


执行:
file:///C:/DOCUME~1/ADMINI~1/LOCALS~1/Temp/msohtml1/01/clip_image028.gif
可以看到,输出的hello后面并没有换行。
puts函数返回值同fputs.
puts的使用:
1.    #include   
2.    #include   
3.      
4.    #define PRINT_D(intValue)   printf(#intValue" is %lu\n", (intValue));  
5.    #define PRINT_CH(ch)        printf(#ch" is %c\n", (ch));  
6.    #define PRINT_STR(str)      printf(#str" is %s\n", (str));  
7.      
8.    int main (int argc, const char * argv[])  
9.    {   
10.      int ret = puts("hello");  
11.      if(ret != EOF)  
12.      {  
13.          PRINT_D(ret)  
14.      }  
15.        
16.      return 0;  
17.  }  


运行结果:
file:///C:/DOCUME~1/ADMINI~1/LOCALS~1/Temp/msohtml1/01/clip_image030.gif
可以看到puts输出后自动加了换行。
Q: 文件输入和输出可以像printf和scanf一样有格式化操作么?
A: 当然可以了,printf和scanf只是文件输入输出的一个特例而已。
fprintf函数和fscanf函数可以实现此功能。
1.    int  fprintf(FILE * restrict stream, const char * restrict format, ...);  

1.    int  fscanf(FILE *restrict stream, const char *restrict format, ...);  


可以看到,它们和printf和scanf很相似,除了第一个参数表示文件指针,对于printf也就是fprintf(stdout, xxx)的特例;scanf也就是fscanf(stdin, xxx)的特例。
不过,fprintf还可以用文件指针stderr表示错误输出,它是不带缓冲的输出。
fprintf的返回值表示成功输出的字符字节个数,printf的返回值和它一致。
fscanf返回值表示成功输入的变量个数。
这里不做更多说明了。
Q: 一直提到的文件句柄,它到底是什么?
A:
1.    typedef struct __sFILE {  
2.        unsigned char *_p;  /* current position in (some) buffer */  
3.        int _r;     /* read space left for getc() */  
4.        int _w;     /* write space left for putc() */  
5.        short   _flags;     /* flags, below; this FILE is free if 0 */  
6.        short   _file;      /* fileno, if Unix descriptor, else -1 */  
7.        struct  __sbuf _bf; /* the buffer (at least 1 byte, if !NULL) */  
8.        int _lbfsize;   /* 0 or -_bf._size, for inline putc */  
9.      
10.      /* operations */  
11.      void    *_cookie;   /* cookie passed to io functions */  
12.      int (*_close)(void *);  
13.      int (*_read) (void *, char *, int);  
14.      fpos_t  (*_seek) (void *, fpos_t, int);  
15.      int (*_write)(void *, const char *, int);  
16.   
17.      /* separate buffer for long sequences of ungetc() */  
18.      struct  __sbuf _ub; /* ungetc buffer */  
19.      struct __sFILEX *_extra; /* additions to FILE to not break ABI */  
20.      int _ur;        /* saved _r when _r is counting ungetc data */  
21.   
22.      /* tricks to meet minimum requirements even when malloc() fails */  
23.      unsigned char _ubuf[3]; /* guarantee an ungetc() buffer */  
24.      unsigned char _nbuf[1]; /* guarantee a getc() buffer */  
25.   
26.      /* separate buffer for fgetln() when line crosses buffer boundary */  
27.      struct  __sbuf _lb; /* buffer for fgetln() */  
28.   
29.      /* Unix stdio files get aligned to block boundaries on fseek() */  
30.      int _blksize;   /* stat.st_blksize (may be != _bf._size) */  
31.      fpos_t  _offset;    /* current lseek offset (see WARNING) */  
32.  } FILE;  


可以看到它封装了操作文件的缓冲区、当前位置等信息,这也能很好地体现带缓冲的事实。比如,fopen函数,它和open函数的一大区别就是是否使用用户层缓冲。
Q: fopen和open的关系是什么样子的?
A: open是POSIX标准,也是基于Unix系统的系统调用。fopen是C语言标准,它的内部当然必须调用open系统调用才能完成真正功能。
Q: 给个使用open, read, write, close函数的例子吧。
A:
首先使用echo命令创建一个内容为hello的文件,文件名为test.
1.    echo hello > test  


然后编写如下代码:
1.    #include   
2.    #include   
3.    #include   
4.    #include   
5.      
6.    #define PRINT_D(intValue)   printf(#intValue" is %lu\n", (intValue));  
7.    #define PRINT_CH(ch)        printf(#ch" is %c\n", (ch));  
8.    #define PRINT_STR(str)      printf(#str" is %s\n", (str));  
9.      
10.  int main (int argc, const char * argv[])  
11.  {  
12.      char buf[32] = {0};  
13.      ssize_t ret;  
14.      int file = open("test", O_RDONLY);  
15.   
16.      if(file < 0)  
17.      {  
18.          perror("open file error");  
19.          return -1;  
20.      }  
21.      ret = read(file, buf, sizeof(buf));  
22.      if(ret > 0)  
23.          PRINT_STR(buf)  
24.            
25.      close(file);  
26.        
27.      return 0;  
28.  }  


运行,结果为:
file:///C:/DOCUME~1/ADMINI~1/LOCALS~1/Temp/msohtml1/01/clip_image032.gif
Q: 我想在文件开头插入一个字符,用如下的代码怎么不行?
1.    #include   
2.    #include   
3.    #include   
4.    #include   
5.      
6.    #define PRINT_D(intValue)   printf(#intValue" is %lu\n", (intValue));  
7.    #define PRINT_CH(ch)        printf(#ch" is %c\n", (ch));  
8.    #define PRINT_STR(str)      printf(#str" is %s\n", (str));  
9.      
10.  int main (int argc, const char * argv[])  
11.  {  
12.      FILE *fp = fopen("test", "r+");  
13.      if(!fp)  
14.      {  
15.          perror("fopen error");  
16.          return -1;  
17.      }  
18.      fseek(fp, 0, SEEK_SET);  
19.      if(fputc('a', fp) == EOF)  
20.          perror("fputc error");  
21.        
22.      fclose(fp);  
23.        
24.      return 0;  
25.  }  


本来test文件里面的内容是hello\n,执行上面的程序后,为什么不是ahello\n,而是aello\n ?
A: 这个问题的原因在于fputc, fputs, fwrite等写操作的函数均为覆盖写,不是插入写导致的。如果需要将文件读出来,将文件开头插入字符a,然后将读出的数据全部追加到文件后面,这样才行。
Q: 一直听到二进制文件和文本文件,它们到底有什么区别?
A: 计算机底层最终只能处理所谓的二进制文件,文本文件只是人们将文件的内容做了抽象得到的一种特殊的二进制文件。不过,它们是有一定区别的,可能在某些时候,不同的平台,它们的表现也不同。但是从理论上来说,二进制文件可能更节省空间,这一方面是因为二进制文件是文件最终的形式,另一方面是因为可以用二进制的1比特表示数值1,甚至整形1,但是用文本方式,却至少用1字节。另外,不同平台对于回车换行,CR与LF, "\r\n"的表示方式不太一致,导致对于它的文本解读和二进制形式有不一致的地方。在mac上,文本形式的换行用'\n'表示,而在windows上,文本换行是\r\n, 所以有时会发现同一个文件在不同操作系统下的换行显示会有不同。不过,二进制和文本方式读取也不是水火不相容的,正如下面的例子,显示的结论没什么不同:
假设test文件中保存hello\n,
文本方式读:
1.    #include   
2.    #include   
3.    #include   
4.    #include   
5.      
6.    #define PRINT_D(intValue)   printf(#intValue" is %lu\n", (intValue));  
7.    #define PRINT_CH(ch)        printf(#ch" is %c\n", (ch));  
8.    #define PRINT_STR(str)      printf(#str" is %s\n", (str));  
9.      
10.  int main (int argc, const char * argv[])  
11.  {  
12.      int ch;  
13.      FILE *fp = fopen("test", "rt");  
14.      if(!fp)  
15.      {  
16.          perror("fopen error");  
17.          return -1;  
18.      }  
19.      while ((ch = fgetc(fp)) != EOF)  
20.      {  
21.          printf("ch:%c %d\n", ch == '\n' ? 'N' : ch, ch);  
22.      }  
23.        
24.      fclose(fp);  
25.        
26.      return 0;  
27.  }  


二进制读:
1.    #include   
2.    #include   
3.    #include   
4.    #include   
5.      
6.    #define PRINT_D(intValue)   printf(#intValue" is %lu\n", (intValue));  
7.    #define PRINT_CH(ch)        printf(#ch" is %c\n", (ch));  
8.    #define PRINT_STR(str)      printf(#str" is %s\n", (str));  
9.      
10.  int main (int argc, const char * argv[])  
11.  {  
12.      int ch;  
13.      FILE *fp = fopen("test", "rb");  
14.      if(!fp)  
15.      {  
16.          perror("fopen error");  
17.          return -1;  
18.      }  
19.      while ((ch = fgetc(fp)) != EOF)  
20.      {  
21.          printf("ch:%c %d\n", ch == '\n' ? 'N' : ch, ch);  
22.      }  
23.        
24.      fclose(fp);  
25.        
26.      return 0;  
27.  }  


二个应用程序的运行结果均为:
file:///C:/DOCUME~1/ADMINI~1/LOCALS~1/Temp/msohtml1/01/clip_image034.gif
显示为N的地方为换行字符。
Q: 有的时候,想要直接将一个整形数据以二进制形式写入文件中,有什么更分便的调用方式?
A: putw可以实现你要的功能。
1.    #include   
2.    #include   
3.    #include   
4.    #include   
5.      
6.    #define PRINT_D(intValue)   printf(#intValue" is %lu\n", (intValue));  
7.    #define PRINT_CH(ch)        printf(#ch" is %c\n", (ch));  
8.    #define PRINT_STR(str)      printf(#str" is %s\n", (str));  
9.      
10.  int main (int argc, const char * argv[])  
11.  {  
12.      FILE *fp = fopen("test", "w");  
13.      if(!fp)  
14.      {  
15.          perror("fopen error");  
16.          return -1;  
17.      }  
18.      putw(32767, fp);  
19.   
20.      fclose(fp);  
21.        
22.      return 0;  
23.  }  


运行完后,用hexdump test查看test文件的十六进制形式,可以发现32767对应int类型大小的数据已经保存在test中了。
当然,如果不嫌麻烦,可以使用如下的代码实现上面类似的功能:
1.    #include   
2.    #include   
3.    #include   
4.    #include   
5.      
6.    #define PRINT_D(intValue)   printf(#intValue" is %lu\n", (intValue));  
7.    #define PRINT_CH(ch)        printf(#ch" is %c\n", (ch));  
8.    #define PRINT_STR(str)      printf(#str" is %s\n", (str));  
9.      
10.  int main (int argc, const char * argv[])  
11.  {  
12.      int n = 32767;  
13.      char *pn = (char *)&n;  
14.      int i;  
15.      FILE *fp = fopen("test", "w");  
16.      if(!fp)  
17.      {  
18.          perror("fopen error");  
19.          return -1;  
20.      }  
21.      for(i = 0; i < sizeof(n); ++i)  
22.      {  
23.          fputc(*(pn + i), fp);  
24.      }  
25.   
26.      fclose(fp);  
27.        
28.      return 0;  
29.  }  

Q: 之前看过好多关于文件打开方式的字符串,"rb", "w+"等等,到底怎么很好地明白它们的打开方式?
A: 其实很简单。以r开头的方式打开,如果文件不存在必然失败;打开方式含有b即表示是二进制方式,如果没有b或者有t,那就说明是文本方式;如果打开方式中有+,那么表示可读可写;
Q: 很多时候,如果读取输入缓冲区的数据读多了,想放回去怎么办?
A: 可以使用ungetc函数。
1.    int  ungetc(int, FILE *);  


举个例子吧:
从标准输入读取一个十进制整数和最后一个分隔符(不是十进制整数字符),然后打印这个整数和最后的分隔符。
1.    #include   
2.    #include   
3.    #include   
4.    #include   
5.    #include   
6.      
7.    #define PRINT_D(intValue)   printf(#intValue" is %lu\n", (intValue));  
8.    #define PRINT_CH(ch)        printf(#ch" is %c\n", (ch));  
9.    #define PRINT_STR(str)      printf(#str" is %s\n", (str));  
10.   
11.  int main (int argc, const char * argv[])  
12.  {  
13.      int n = 0;  
14.      int ch;  
15.      char end_ch;  
16.        
17.      while ((ch = getchar()) != EOF && isdigit(ch))  
18.      {  
19.          n = 10 *n + (ch - '0');  
20.      }  
21.      if(ch != EOF)  
22.      {  
23.          ungetc(ch, stdin);  
24.      }  
25.      end_ch = getchar();  
26.        
27.      PRINT_D(n)  
28.      PRINT_CH(end_ch)  
29.        
30.      return 0;  
31.  }  


运行时,输入1234;然后换行,
file:///C:/DOCUME~1/ADMINI~1/LOCALS~1/Temp/msohtml1/01/clip_image036.gif
可以发现,ungetc会将;字符重新放回输入流中,是的end_ch被赋值为;字符;
如果没有使用ungetc的话:
file:///C:/DOCUME~1/ADMINI~1/LOCALS~1/Temp/msohtml1/01/clip_image038.gif
可以发现,end_ch被赋值为输入1234;后面的换行了。
Q: 关于输入输出重定向已经听说了很多了,c语言中有函数可以实现重定向吗?
A: 是的, freopen函数可以实现这个作用。
1.    FILE *freopen(const char *restrict filename, const char *restrict mode, FILE *restrict stream);  


第一个参数表示需要重定向的文件位置;第二个参数表示重定向方式,如"w"为写方式;第三个参数表示被重定向的文件句柄;
下面有个代码将展示如何将stdout重定向到test文件,然后再恢复stdout的输出;
代码如下:
1.    #include   
2.    #include   
3.    #include   
4.    #include   
5.    #include   
6.      
7.    #define PRINT_D(intValue)   printf(#intValue" is %lu\n", (intValue));  
8.    #define PRINT_CH(ch)        printf(#ch" is %c\n", (ch));  
9.    #define PRINT_STR(str)      printf(#str" is %s\n", (str));  
10.   
11.  int main (int argc, const char * argv[])  
12.  {  
13.      FILE *file;  
14.      int fd;  
15.      fpos_t  pos;  
16.        
17.      // output to stdout  
18.      fprintf(stdout, "1111");  
19.        
20.      // backup the stdout info  
21.      fflush(stdout);  
22.      fgetpos(stdout, &pos);  
23.      fd = dup(fileno(stdout));  
24.   
25.      // redirect stdout to "test" file  
26.      file = freopen("test", "w", stdout);  
27.      if(!file)  
28.      {  
29.          perror("freopen error");  
30.          return -1;  
31.      }  
32.        
33.      // now the stdout is redirected to "test" file, file "test" will contain "hello" str  
34.      fprintf(stdout, "hello");  
35.        
36.      // restore the stdout  
37.      fflush(stdout);  
38.      dup2(fd, fileno(stdout));  
39.      close(fd);  
40.      clearerr(stdout);  
41.      fsetpos(stdout, &pos);  
42.        
43.      // now the stdout is as the default state  
44.      fprintf(stdout, "2222");  
45.        
46.      return 0;  
47.  }  

运行结果:
file:///C:/DOCUME~1/ADMINI~1/LOCALS~1/Temp/msohtml1/01/clip_image040.gif
可以看出,最开始的标准输出和最后的标准输出都正常显示在屏幕,第二个标准输出因为被重定向输出到了test文件中;查看test文件的内容:
file:///C:/DOCUME~1/ADMINI~1/LOCALS~1/Temp/msohtml1/01/clip_image042.gif
不过,如果是在windows平台,恢复stdout的方式和上面的代码可能不一致。

[ 本帖最后由 tiankai001 于 2012-12-15 07:34 编辑 ]
此帖出自单片机论坛
 
 
 

回复

6366

帖子

4929

TA的资源

版主

5
 
Q: 预处理到底干了什么事情?
A: 预处理,顾名思义,预先做的处理。源代码中的头文件包含,宏以及条件编译的东西都会在预处理的时候搞定。换句话说,以#开头的语句即为预处理。但是,如果#被包含在引号里面,那就只是单纯的字符或者字符串了。
Q: 怎么证明预处理的存在?
A: 如下代码,保存为macro.c:
1.    #include   
2.      
3.    #define NUM 100  
4.      
5.    int main()  
6.    {  
7.        printf("%d\n", NUM);  
8.        return 0;  
9.    }  


使用gcc  -E macro.c  -o  macro.i得到预处理的结果(因为篇幅问题,只截取最后数行代码):
1.    # 500 "/usr/include/stdio.h" 2 3 4  
2.    # 2 "macro.c" 2  
3.      
4.      
5.      
6.    int main()  
7.    {  
8.     printf("%d\n", 100);  
9.     return 0;  
10.  }  

可以看到,源代码中的NUM已经被替换成了宏定义的100.
事实上,预处理中的宏同样可以在命令行中指定,如下代码,保存为macro.c:
1.    #include   
2.      
3.    int main()  
4.    {  
5.        printf("%d\n", NUM);  
6.        return 0;  
7.    }  


可以看到代码中的NUM没有被定义,然后使用编译命令加上对NUM的宏定义:
gcc  -DNUM=100  macro.c  -o macro
编译结束,没有问题,运行亦ok.
同理,对于头文件包含以及条件编译都可以通过预处理命令得到处理之后的代码形式,这样会更好地理解预处理的含义。调试宏也不是一个简单的事情,如果很难确定某个宏到底有没有作用或者宏对应的字符串到底是什么的时候,使用预处理命令得到结果是很好的方式。
Q: 有时看到一个字符串前面带有一个#符号,它表示什么含义?
形如:
1.    #define PRINT_CH(ch)        printf(#ch" is %c\n", (ch));  

A:它表示将对应字符组合转换成相应的字符串格式。
使用如下代码,保存为macro.c:
1.    #include   
2.      
3.    #define PRINT_CH(ch)    printf(#ch" is %c\n", (ch));  
4.      
5.    int main()  
6.    {  
7.        PRINT_CH('a')  
8.        return 0;  
9.    }  


使用gcc  -E  -o macro.i  macro.c编译命令得到预处理后的文件macro.i.
使用如下命令得到预处理文件的最后10行:
file:///C:/DOCUME~1/ADMINI~1/LOCALS~1/Temp/msohtml1/01/clip_image002.gif
可以看到PRINT_CH('a')宏被处理成: printf("'a'""is %c\n", ('a'));
#ch的作用就是将它变成"ch"的模样。两个字符串字面量放一起相当于字符串拼接的作用。编译和运行结果:
file:///C:/DOCUME~1/ADMINI~1/LOCALS~1/Temp/msohtml1/01/clip_image004.gif
Q: 除了上面的#符号,还会看到##符号,它又是什么含义?
A: 它是代表参数连接,连接过程很单纯,让你看不到一点改变。举个例子:
1.    #include   
2.    #define CATENATE(a, b)  a##b  
3.      
4.    int main()  
5.    {  
6.        int ab = 1;  
7.        printf("%d\n", CATENATE(a, b));  
8.        return 0;  
9.    }  


保存为preprocess.c,预处理后的结果是(仅截取最后数行代码):
1.    # 2 "preprocess.c" 2  
2.      
3.      
4.    int main()  
5.    {  
6.     int ab = 1;  
7.     printf("%d\n", ab);  
8.     return 0;  
9.    }  

编译运行:
file:///C:/DOCUME~1/ADMINI~1/LOCALS~1/Temp/msohtml1/01/clip_image006.gif
可以发现, a##b得到的结果就是ab这个符号。
Q: 宏定义也是可以续行的,为什么有的时候用的反斜杠来续行,最后编译依然有错?
A: 这有可能续行符被用在了字符串字面量里面,这个不允许的;或者续行符后面跟着非换行字符导致的。续行的含义是将随后紧随的换行字符干掉,当成没发生,如果不是换行字符就可能导致错误。不过gcc 4.2对这个要求也不是很严格了,如下代码,保存为preprocess.c:
1.    #include   
2.    #define ADD(a, b)   \   
3.        ((a) + (b))  
4.      
5.    int main()  
6.    {  
7.        int ret = ADD(1, 2);  
8.        printf("%d\n", ret);  
9.      
10.      return 0;  
11.  }  


其中ADD宏行末的续行符后面有一个空格,在有的编译器下会编译错误。
为了确认续行符后面是否有空格,使用cat  -e  preprocess.c命令得到行末信息:
file:///C:/DOCUME~1/ADMINI~1/LOCALS~1/Temp/msohtml1/01/clip_image008.gif
可以看到第二行的续行符后面有个空格。编译它,没有什么问题。
Q: 看到printf函数是可变参数的,如果用宏可以定义吗?
A: 是的。__VA_ARGS__便是可变参数的代表。如下代码:
1.    #include   
2.      
3.    #define printf_ex(...)  printf(__VA_ARGS__)  
4.      
5.    int main (int argc, const char * argv[])  
6.    {  
7.        printf_ex("hello%d\n", 1);  
8.        return 0;  
9.    }  


上面的代码宏定义了printf_ex函数,参数为可变参数,它的实现即为printf函数,__VA_ARGS__即为printf_ex的可变参数。
编译运行:
file:///C:/DOCUME~1/ADMINI~1/LOCALS~1/Temp/msohtml1/01/clip_image010.gif
Q: 既然可以宏定义,那么重复的宏定义的结果是什么呢?
A: 这样的话,一般预处理器会按照后者定义的为准,不过一般的预处理器也会发出警告。
如下代码,保存为redefinition.c:
1.    #include   
2.      
3.    #define A   10  
4.    #define A   11  
5.      
6.    #define PRINT_D(intValue)   printf(#intValue" is %d\n", (intValue));  
7.      
8.    int main (int argc, const char * argv[])  
9.    {  
10.      PRINT_D(A)  
11.      return 0;  
12.  }  

编译:
file:///C:/DOCUME~1/ADMINI~1/LOCALS~1/Temp/msohtml1/01/clip_image012.gif
可以发现出现了A重复宏定义的警告。
运行:
file:///C:/DOCUME~1/ADMINI~1/LOCALS~1/Temp/msohtml1/01/clip_image014.gif
Q: 有的时候发现需要打印当前运行的位置,比如文件名,行数以及运行所在的函数,这该怎么办?
A: 可以使用__FILE__, __LINE__和__func__这些预定义的符号来处理。这里以__func__的使用为例子:
1.    #include   
2.      
3.    int add(int a, int b)  
4.    {  
5.        printf("func %s execute begin...", __func__);  
6.        return a + b;  
7.    }  
8.      
9.    int main (int argc, const char * argv[])  
10.  {  
11.      add(1, 2);  
12.      return 0;  
13.  }  


编译运行:
file:///C:/DOCUME~1/ADMINI~1/LOCALS~1/Temp/msohtml1/01/clip_image016.gif
可以看到add函数里面的__func__被替换成了add.


[ 本帖最后由 tiankai001 于 2012-12-15 07:35 编辑 ]
此帖出自单片机论坛
 
 
 

回复

6366

帖子

4929

TA的资源

版主

6
 
本文代码编写编译运行的环境:[Mac-10.7.1 LionIntel-based]
Q: 有的时候总是发现一个数组的字符串可以修改,但是如果使用字符串字面量就不能修改,这是为什么?
1.    #include   
2.      
3.    int main()  
4.    {  
5.        char buf[] = "hello";  
6.        char *str = "hello";  
7.      
8.        buf[0] = 'a';  
9.        str[0] = 'a';  
10.      return 0;  
11.  }  


代码运行:
file:///C:/DOCUME~1/ADMINI~1/LOCALS~1/Temp/msohtml1/01/clip_image002.gif
而且是运行到str[0] ='a'; 的时候挂掉的。
A: 这是因为buf数组的数据存放在栈中,而str指向的字符串数据保存在全局只读数据区域。str[0] = 'a'; 修改了不能被修改的数据块。
Q: 怎么才能知道char *str = "hello";这句代码中的hello字符串被保存在全局只读数据区域里呢?
A: 我们可以使用strings命令来得到上面代码编译成的可执行文件里面的可打印字符串。
假设上面的代码保存为char_string.c, 编译: gcc  -o char_string  char_string.c
先看下strings程序的作用:
file:///C:/DOCUME~1/ADMINI~1/LOCALS~1/Temp/msohtml1/01/clip_image004.gif
接着用strings char_string得到char_string可执行文件内部的可打印字符串:
file:///C:/DOCUME~1/ADMINI~1/LOCALS~1/Temp/msohtml1/01/clip_image006.gif
Q: 那么怎么证明char buf[] = "hello"; 中buf保存的数据hello在栈中呢?
A: 使用gcc  -S  char_string.c得到它的汇编形式:
1.    movb    L_.str(%rip), %al  
2.        movb    %al, -14(%rbp)  
3.        movb    L_.str+1(%rip), %al  
4.        movb    %al, -13(%rbp)  
5.        movb    L_.str+2(%rip), %al  
6.        movb    %al, -12(%rbp)  
7.        movb    L_.str+3(%rip), %al  
8.        movb    %al, -11(%rbp)  
9.        movb    L_.str+4(%rip), %al  
10.      movb    %al, -10(%rbp)  
11.      movb    L_.str+5(%rip), %al  
12.      movb    %al, -9(%rbp)  
13.      leaq    L_.str(%rip), %rax  
14.      movq    %rax, -24(%rbp)  
15.      movb    $97, -14(%rbp)  


其中L_.str为:
1.    L_.str:  
2.        .asciz   "hello"  

可以看到,上面的汇编代码中的%rbp即为和堆栈基址相对应的寄存器。
Q: 常常看到,如果arr是个数组,arr和*(arr + i)是等同的, 这里的i可以为负数吗?
A: 是的。数组就是个数据块,至于是取arr更高地址还是更低地址的数据,这有程序员决定。当然,arr表示数组的初始地址,取比它更低的地址可能不是程序员的本来意图,小心为之。
1.    #include   
2.    #define PRINT_D(intValue)   printf(#intValue" is %d\n", (intValue));  
3.      
4.    int main()  
5.    {  
6.        int arr[] = {1, 2};  
7.        int n = 3;  
8.        PRINT_D(arr[0])  
9.        PRINT_D(arr[-1])  
10.      return 0;  
11.  }  

编译运行:
file:///C:/DOCUME~1/ADMINI~1/LOCALS~1/Temp/msohtml1/01/clip_image008.gif
可以看到,arr[0]打印预期的1, arr[-1]输出数组arr地址更低4个字节(笔者的平台的int是4个字节)空间数据的整形值。依据栈的原理,变量n正好保存在这个位置,所以会输出3.
Q: 经常看到多维数组的形式,发现它的形式还有关于多维数组相关变量的地址,不是很好地分析,如何很好地认识?
A: 如下例子:
1.    #include   
2.    #define PRINT_P(pointer)   printf("%10s is %p\n", #pointer, (pointer));  
3.      
4.    int main (int argc, const char * argv[])  
5.    {  
6.        int arr[2][3] = {1, 2, 3, 4, 5, 6};  
7.        PRINT_P(arr)  
8.        PRINT_P(&arr)  
9.        PRINT_P(arr[0])  
10.      PRINT_P(&arr[0])  
11.      PRINT_P(arr[1])  
12.      PRINT_P(&arr[1])  
13.        
14.      return 0;  
15.  }  


运行结果:
file:///C:/DOCUME~1/ADMINI~1/LOCALS~1/Temp/msohtml1/01/clip_image010.gif
可以看到, arr和&arr是一致的,因为arr就是代表此数据的地址;它的地址依然和它一样;而且arr的虚拟地址是在编译阶段即可确定。arr[0]也是一个地址,因为arr是二维数组,所以&arr[0]和arr[0]也是一致的;arr[0]即是arr初始一块数据的地址,所以它们也是一致的;同理可分析,arr[1]和&arr[1].
Q: 对于多维数组的形式,有的时候也有些不好理解;先拿简单的一维数组来说,数组作为参数的形式是怎么样的?
A: 数组作为参数,只需要将首地址传入即可,当然一般还可能需要数组元素个数的参数。形如:
1.    #include   
2.    #define PRINT_P(pointer)   printf("%10s is %p\n", #pointer, (pointer));  
3.    #define PRINT_D(intValue)   printf(#intValue" is %d\n", (intValue));  
4.      
5.    void    print_arr(int *arr, int size)  
6.    {  
7.        int i = 0;  
8.        for(; i < size; ++i)  
9.            PRINT_D(arr)  
10.  }  
11.   
12.  int main (int argc, const char * argv[])  
13.  {  
14.      int arr[] = {1, 2, 3};  
15.      print_arr(arr, sizeof(arr) / sizeof(arr[0]));  
16.      return 0;  
17.  }  


因为数组元素是整形的,所以数组地址为整形指针,即为上面的int *, 还有个参数size表示数组的大小。
Q: 既然数组的个数可以用sizeof(arr) / sizeof(arr[0])来表示,那么参数size不就是不必要的吗,print_arr函数里面直接用这个表达式不就可以得到数组的大小了么?
A: 测试下。
1.    #include   
2.    #define PRINT_P(pointer)   printf("%10s is %p\n", #pointer, (pointer));  
3.    #define PRINT_D(intValue)   printf(#intValue" is %d\n", (intValue));  
4.      
5.    void    print_arr(int *arr)  
6.    {  
7.        int i = 0;  
8.        for(; i < sizeof(arr) / sizeof(arr[0]); ++i)  
9.            PRINT_D(arr)  
10.  }  
11.   
12.  int main (int argc, const char * argv[])  
13.  {  
14.      int arr[] = {1, 2, 3};  
15.      print_arr(arr);  
16.      return 0;  
17.  }  


运行:
file:///C:/DOCUME~1/ADMINI~1/LOCALS~1/Temp/msohtml1/01/clip_image012.gif
Q: 为何数组arr的个数不正确了?
A: 我们可以看看print_arr函数的汇编形式:
1.    0x0000000100000dd0 : push   %rbp  
2.    0x0000000100000dd1 : mov    %rsp,%rbp  
3.    0x0000000100000dd4 : sub    $0x10,%rsp  
4.    0x0000000100000dd8 : mov    %rdi,-0x8(%rbp)  
5.    0x0000000100000ddc :    movl   $0x0,-0xc(%rbp)  
6.    0x0000000100000de3 :    movslq -0xc(%rbp),%rax  
7.    0x0000000100000de7 :    cmp    $0x2,%rax  
8.    0x0000000100000deb :    jae    0x100000e14   
9.    0x0000000100000ded :    lea    0xa4(%rip),%rdi        # 0x100000e98  
10.  0x0000000100000df4 :    movslq -0xc(%rbp),%rax  
11.  0x0000000100000df8 :    mov    -0x8(%rbp),%rcx  
12.  0x0000000100000dfc :    mov    (%rcx,%rax,4),%esi  
13.  0x0000000100000dff :    mov    $0x0,%al  
14.  0x0000000100000e01 :    callq  0x100000e6e   
15.  0x0000000100000e06 :    mov    %eax,-0x10(%rbp)  
16.  0x0000000100000e09 :    mov    -0xc(%rbp),%eax  
17.  0x0000000100000e0c :    add    $0x1,%eax  
18.  0x0000000100000e0f :    mov    %eax,-0xc(%rbp)  
19.  0x0000000100000e12 :    jmp    0x100000de3   
20.  0x0000000100000e14 :    add    $0x10,%rsp  
21.  0x0000000100000e18 :    pop    %rbp  
22.  0x0000000100000e19 :    retq     


可以看到第七行位置的cmp指令和第八行的jae指令,表示如果循环变量i大于或者等于2,那么跳到结束位置。也就是说,这个函数里面,把arr数组大小看成了2, 这是为什么呢?这是因为编译器编译print_arr函数代码时,根本不知道传入int * arr参数的到底是个数组还是指针,所以sizeof(arr) / sizeof(arr[0])得到的是sizeof(int *) /sizeof(int)的值(笔者平台得到的是2)。其实这也是数组名作为参数的一个可能引发错误的地方。
Q: 那么如果把参数形式int *arr改为int arr[]就能将arr当成数组了,代码就会正确执行?
A: 很可惜,c语言的语法决定了任何代码都会编译成确定指令的东西,和上面说的一样,print_arr依然不知道外部传入参数arr的数组或者指针到底有多大,sizeof(arr) / sizeof(arr[0])最终又被得到一个诡异的数据。
Q: 那该怎么办?
A: 就另外传入一个参数为传入数组的元素个数即可。
Q: 关于二维数组,经常看到一些很诡异的样式,到底怎么很好地理解?
A: 形如如下代码:
1.    #include   
2.    #define PRINT_P(pointer)    printf("%10s is %p\n", #pointer, (pointer));  
3.    #define PRINT_D(intValue)   printf(#intValue" is %d\n", (intValue));  
4.      
5.    int main (int argc, const char * argv[])  
6.    {  
7.        int arr[] = {1, 2};  
8.        int (*p_arr)[2] = &arr;  
9.        PRINT_D(**p_arr)  
10.        
11.      return 0;  
12.  }  


运行结果:
file:///C:/DOCUME~1/ADMINI~1/LOCALS~1/Temp/msohtml1/01/clip_image014.gif
可以看到, p_arr是一个指针,它指向一个包含2个整形元素的数组;arr数组正好满足要求,所以p_arr可以指向它。这里需要注意,p_arr的值是arr的地址,所以使用它的时候需要解引用。*p_arr表示数组arr, *(*p_arr)表示*arr, 也就是arr[0], 所以最后输出数值1.
Q: 这里使用p_arr太浪费了,直接用arr比它简单多了!
A: 是的, 数组指针更多地可以用到二维或者多维数组更能体现价值。如下代码:
1.    #include   
2.    #define PRINT_P(pointer)    printf("%10s is %p\n", #pointer, (pointer));  
3.    #define PRINT_D(intValue)   printf(#intValue" is %d\n", (intValue));  
4.      
5.    int main (int argc, const char * argv[])  
6.    {  
7.        int arr[][2] = {{1, 2}, {3, 4}};  
8.        int (*p_arr)[2] = &arr[0];  
9.        PRINT_D(p_arr[1][1])  
10.      p_arr = &arr[1];  
11.      PRINT_D(p_arr[0][1])  
12.        
13.      return 0;  
14.  }  


运行结果:
file:///C:/DOCUME~1/ADMINI~1/LOCALS~1/Temp/msohtml1/01/clip_image016.gif
可以看到,arr是二维数组,arr[0]是一个一维数组, &arr[0]是一维数组指针,p_arr是个指针,它需要指向一个包含2个元素的数组, &arr[0]正好符合,所以int (*p_arr)[2] =&arr[0]; 代码ok; 紧接着,p_arr[1]表示p_arr指向的一维数组为单位的下一个数组,也就是arr[1]所在的数组; p_arr[1][1]也就等同于arr[1][1], 所以结果打印4;我想,你可以分析后两句代码的意图了。
同时,你也可以看到,上面两段代码,同是int (*p_arr)[2],可以指向单纯的一维数组,同时也可以指向二维数组中的一维数组,这就是指针,只要类型ok,就能指。
Q: 下面使用二维数组以及它的指针,为什么会挂掉?
1.    #include   
2.    #define PRINT_P(pointer)    printf("%10s is %p\n", #pointer, (pointer));  
3.    #define PRINT_D(intValue)   printf(#intValue" is %d\n", (intValue));  
4.      
5.    int main (int argc, const char * argv[])  
6.    {  
7.        int arr[][2] = {{1, 2}, {3, 4}};  
8.        int **p_arr = (int **)arr;  
9.        PRINT_D(p_arr[0][0])  
10.        
11.      return 0;  
12.  }  

运行结果:
file:///C:/DOCUME~1/ADMINI~1/LOCALS~1/Temp/msohtml1/01/clip_image018.gif
A: arr是二维数组,arr[0][0]是没问题的;p_arr是二级指针,它指向arr,也就是p_arr的值是arr.所以p_arr[0]就是以地址p_arr为地址的数据,也就是arr数组的第一个元素,即p_arr[0] 等于1.那么p_arr[0][0]就是地址为1的一个整形数据,这能保证不挂么?
Q: 有的时候,发现下面这样的声明实在太难解读了,有什么好的方法么?
1.    int (*p[3])(int *arg);  
2.    int  (*(*func)(int  *p))[3];  
3.    int (*(*func)[3])(int *p);  

A: 要读懂这些函数,需要掌握优先级,函数指针的知识。
一一解析:
第一个:(*p[3]),[]优先级比*高,所以p是一个数组,含有3个元素,*表示数组元素都是指针;接着,看到右边(int *arg)表明前面的是个函数,参数是int *类型, 最左边的int表示返回值为整形;最后得到:p是一个数组,它含有3个元素,每个元素都是函数指针,函数指针的格式是: int (*)(int *arg);
由上,代码例子:
1.    #include   
2.      
3.    int (*func1)(int *arg);  
4.    int (*func2)(int *arg);  
5.    int (*func3)(int *arg);  
6.      
7.    int main (int argc, const char * argv[])  
8.    {  
9.        int (*p[3])(int *arg) = {func1, func2, func3};  
10.        
11.      return 0;  
12.  }  


第二个:*func表示func是一个指针,后面的int *p表示,它是一个函数指针,参数为int *p, 左侧一个星号,表示返回值是个指针,右侧[3]表示返回值是个3个元素的数组,每个元素都是指针,最左侧的int表示返回值的数组元素为整形。总结下:func是个函数指针,参数为int *p, 返回值为包含5个元素的数组,且为指针。
第三个: 类似第一个,不过func多了一个指针类型。总结:func是一个指针,它指向一个数组,数组元素个数为3,每个元素都是一个函数指针,函数指针参数为int *p, 返回值为int.
xichen
2012-5-14 12:46:50

[ 本帖最后由 tiankai001 于 2012-12-15 07:37 编辑 ]
此帖出自单片机论坛
 
 
 

回复

6366

帖子

4929

TA的资源

版主

7
 
[Mac-10.7.1Lion Intel-based, gcc 4.2.1]
Q: 指针到底是什么?
A: 有一天,你初到合肥,要到一个地方,但是路线不熟悉。遂问了一个路人,请问乌邦托在哪里?路人答曰:向南走即可。这个回答就像指针一样,告诉你一个方向。但是,到底向南多少,这就像是指针类型决定的大小。
Q: 看到那个const修饰指针的时候,老是搞不清楚到底是指针是常量还是指针指向的变量是常量?
A: 其实很简单,采用从右向左的读法即可搞定这些。如下例子:
1.    #include   
2.      
3.    int main (int argc, const char * argv[])  
4.    {  
5.        int i = 100;  
6.        const int *pi = &i;  
7.        *pi = 200;  
8.         
9.        return 0;  
10.  }  

保存为const.c, 使用gcc  -o  const  const.c编译:
file:///C:/DOCUME~1/ADMINI~1/LOCALS~1/Temp/msohtml1/01/clip_image002.gif
可以看到编译错误,表明pi是个不可更改其指向数据的指针。按照上面的从右读的原则即为:
const     int      *         pi
常量      整形   指向   pi
读为: pi指向整形常量,即pi指向什么变量可以改变,但是指向的变量的值不可改变。
当然,使用类型方法,const  int *pi和 int  const  *pi的含义是一致的。
如下代码就是ok的:
1.    #include   
2.      
3.    int main()  
4.    {  
5.        int i = 100, j = 200;  
6.        const int *pi = &i;  
7.        pi = &j;  
8.        return 0;  
9.    }  

编译ok.

另外一种情况:
1.    #include   
2.      
3.    int main()  
4.    {  
5.        int i = 100, j = 200;  
6.        int *const pi = &i;  
7.        pi = &j;  
8.        return 0;  
9.    }  


编译:
file:///C:/DOCUME~1/ADMINI~1/LOCALS~1/Temp/msohtml1/01/clip_image004.gif
可以看到,编译错误表示pi是只读的,不可以更改pi的值。再使用从右向左的读法:
int     *      const    pi
整形  指向   常      pi
读为: pi常指向整形
这也意味着,pi的值不能更改,但是没有意味着pi指向的数据不可以更改。
1.    #include   
2.      
3.    int main()  
4.    {  
5.        int i = 100, j = 200;  
6.        int *const pi = &i;  
7.        *pi = j;  
8.        return 0;  
9.    }  


如上代码,编译ok.
Q: 看过很多代码中含有函数指针,它的本质是什么?
A: 它的本质即为一个指针,理论上函数的地址在编译期即可计算得到(当然在链接或者运行时还可能有重新定位)。先看一个简单的例子:
1.    #include   
2.    #define PRINT_D(intValue)   printf(#intValue" is %d\n", (intValue));  
3.      
4.    int add(int a, int b)  
5.    {  
6.        return a + b;  
7.    }  
8.      
9.    int main()  
10.  {  
11.      int (*func)(int, int) = add;  
12.      PRINT_D(func(1, 2))  
13.      return 0;  
14.  }  


保存为func_ptr.c, 编译运行:
file:///C:/DOCUME~1/ADMINI~1/LOCALS~1/Temp/msohtml1/01/clip_image006.gif
分析下代码:
int (*func)(int, int)表示声明一个函数指针,它的参数是2个整形,返回值为1个整形;什么地方能体现出是函数指针呢?就在于func前面的*号。
=  add;  表示此指针指向add函数。c语言的编译原则是函数编译之后可以确定当前编译状态的地址。为了确定,我们查看下汇编代码:
gcc  -S   func_ptr.c得到汇编(部分):
1.    _add:  
2.    Leh_func_begin1:  
3.        pushq   %rbp  
4.    Ltmp0:  
5.        movq    %rsp, %rbp  
6.    Ltmp1:  
7.        movl    %edi, -4(%rbp)  
8.        movl    %esi, -8(%rbp)  
9.        movl    -4(%rbp), %eax  
10.      movl    -8(%rbp), %ecx  
11.      addl    %ecx, %eax  
12.      movl    %eax, -16(%rbp)  
13.      movl    -16(%rbp), %eax  
14.      movl    %eax, -12(%rbp)  
15.      movl    -12(%rbp), %eax  
16.      popq    %rbp  
17.      ret  
18.  Leh_func_end1:  
19.   
20.      .globl  _main  
21.      .align  4, 0x90  
22.  _main:  
23.  Leh_func_begin2:  
24.      pushq   %rbp  
25.  Ltmp2:  
26.      movq    %rsp, %rbp  
27.  Ltmp3:  
28.      subq    $16, %rsp  
29.  Ltmp4:  
30.      leaq    _add(%rip), %rax  
31.      movq    %rax, -16(%rbp)  
32.      movq    -16(%rbp), %rax  
33.      movl    $1, %ecx  
34.      movl    $2, %edx  
35.      movl    %ecx, %edi  
36.      movl    %edx, %esi  
37.      callq   *%rax  


可以看到_add在汇编代码中是一个标号,标号就意味着是一个地址。callq *%rax可以看到,这是调用add函数。
Q: 用add赋值给func的时候,为什么不用&add, 不是要取函数的地址么?
A: 是的, 这样也可以,不过函数本身就可以看成地址,它们效果一样的。
1.    #include   
2.    #define PRINT_D(intValue)   printf(#intValue" is %d\n", (intValue));  
3.    #define PRINT_P(ptr)        printf("%10s is %p\n", (#ptr), (ptr));  
4.    typedef int (*func)(int, int);  
5.      
6.    int add(int a, int b)  
7.    {  
8.        return a + b;  
9.    }  
10.   
11.  int main()  
12.  {  
13.      PRINT_P(add)  
14.      PRINT_P(&add)  
15.      return 0;  
16.  }  


运行:
file:///C:/DOCUME~1/ADMINI~1/LOCALS~1/Temp/msohtml1/01/clip_image008.gif
Q: int (*func)(int, int)这种写法有点复杂吧。
A: 是的,避免写很多这种代码,可以使用typedef来定义一个函数指针。
1.    #include   
2.    #define PRINT_D(intValue)   printf(#intValue" is %d\n", (intValue));  
3.    typedef int (*func)(int, int);  
4.      
5.    int add(int a, int b)  
6.    {  
7.        return a + b;  
8.    }  
9.      
10.  int main()  
11.  {  
12.      func add_func = add;  
13.      PRINT_D(add_func(1, 2))  
14.      return 0;  
15.  }  



Q: 结构体里面不也是可以含有函数指针的么?这里的函数指针的作用是什么呢?
A: 是的,可以含有。在结构体的函数指针一般为结构体数据服务的,它的实现可以模拟面向对象语言的类的功能。实际上,正因为有了指针,整个编程世界才变得很精彩,很多模拟方式都是通过指针达到的。
1.    #include   
2.    #include   
3.    #define PRINT_D(intValue)   printf(#intValue" is %d\n", (intValue));  
4.    #define PRINT_S(str)        printf(#str" is %s\n", (str));  
5.      
6.    typedef struct Student  
7.    {  
8.        int     age;  
9.        char    name[32];  
10.      int     (*get_age)(struct Student *s);  
11.      void    (*set_age)(struct Student *s, int new_age);  
12.      char    *(*get_name)(struct Student *s);  
13.      void    (*set_name)(struct Student *s, char *new_name);  
14.  }Student;  
15.   
16.  int student_get_age(struct Student *s)  
17.  {  
18.      return s->age;  
19.  }  
20.  void    student_set_age(struct Student *s, int new_age)  
21.  {  
22.      s->age = new_age;  
23.  }  
24.   
25.  char    *student_get_name(struct Student *s)  
26.  {  
27.      return s->name;  
28.  }  
29.   
30.  void    student_set_name(struct Student *s, char *new_name)  
31.  {  
32.      memset(s->name, 0, sizeof(s->name));  
33.      strncpy(s->name, new_name, sizeof(s->name));  
34.  }  
35.   
36.  int main()  
37.  {  
38.      Student s;  
39.      s.get_age = student_get_age;  
40.      s.set_age = student_set_age;  
41.      s.get_name = student_get_name;  
42.      s.set_name = student_set_name;  
43.      student_set_age(&s, 25);  
44.      student_set_name(&s, "xichen");  
45.      PRINT_D(student_get_age(&s))  
46.      PRINT_S(student_get_name(&s))  
47.      return 0;  


编译运行:
file:///C:/DOCUME~1/ADMINI~1/LOCALS~1/Temp/msohtml1/01/clip_image010.gif
Q: 好像这个数组和指针的联系还是挺多的,下面的代码为什么输出的值不对?
1.    #include   
2.    #include   
3.    #define PRINT_D(intValue)   printf(#intValue" is %d\n", (intValue));  
4.      
5.    void    print_arr_size(int arr[3])  
6.    {  
7.        PRINT_D(sizeof(arr))  
8.    }  
9.      
10.  int main()  
11.  {  
12.      int arr[3] = {1, 2, 3};  
13.      print_arr_size(arr);  
14.      return 0;  
15.  }  


运行结果:
file:///C:/DOCUME~1/ADMINI~1/LOCALS~1/Temp/msohtml1/01/clip_image012.gif
A: 这是因为数组形式作为参数会被退化成指针导致的,也就是说print_arr_size函数的参数int arr[3]其实等同于int *arr, 所以sizeof(arr)的值是指针的大小(笔者的平台指针大小为8)。
Q: 常常看到函数的原型中有void *, 它到底代表什么?
A: 比如,
1.    void    *malloc(size_t);  

申请空间,返回对应的指针;但是到底是什么类型的指针,此函数不能确定,是需要外部调用者来确定;所以,void *可以看成通用型指针,其它类型的指针均可以赋值给void*类型指针;而且,void *类型指针都可以通过强制转换变成需要的指针;用面向对象的思想来解释,void*是基类, char *, int *等都是子类。

1.    char *p = (char *)malloc(32);  

其它类型指针转换成void *的例子:

1.    #include   
2.    #define PRINT_D(intValue)   printf(#intValue" is %d\n", (intValue));  
3.      
4.    int main()  
5.    {  
6.        int i = 1;  
7.        void    *p = &i;  
8.        PRINT_D(*(int *)p)  
9.        return 0;  
10.  }  


当然,void *只是表示一种通用指针,到底此指针是什么类型的不确定,所以不能直接对void*类型变量的数据进行直接操作。
'>&��;n p;   
11.      return 0;  
12.  }  

第二个:*func表示func是一个指针,后面的int *p表示,它是一个函数指针,参数为int *p, 左侧一个星号,表示返回值是个指针,右侧[3]表示返回值是个3个元素的数组,每个元素都是指针,最左侧的int表示返回值的数组元素为整形。总结下:func是个函数指针,参数为int *p, 返回值为包含5个元素的数组,且为指针。
第三个: 类似第一个,不过func多了一个指针类型。总结:func是一个指针,它指向一个数组,数组元素个数为3,每个元素都是一个函数指针,函数指针参数为int *p, 返回值为int.
xichen
2012-5-14 12:46:50

[ 本帖最后由 tiankai001 于 2012-12-15 07:39 编辑 ]
此帖出自单片机论坛
 
 
 

回复

6366

帖子

4929

TA的资源

版主

8
 
[Mac-10.7.1Lion Intel-based gcc 4.2.1]
Q: 可以把运算符看成特殊的标识符么?
A: 是的。例如 >= 运算符两个字符之间不能含有空格,这和标识符是类似的。
1.    #include   
2.      
3.    int main()  
4.    {  
5.        1 > = 2;  
6.        return 0;  
7.    }  


编译:
file:///C:/DOCUME~1/ADMINI~1/LOCALS~1/Temp/msohtml1/01/clip_image002.gif
可以看到,> 和 = 符号中间的空格导致了编译器不能理解。
Q: 为什么会产生运算符结合性这个概念?
A: 结合性是在优先级相同的情况下才会进行结合性的判断得到表达式运算的真正的顺序。
Q: 赋值运算符怎么证明确实是右结合性?
A: 如下代码:
1.    #include   
2.      
3.    int main()  
4.    {  
5.        int a, b, c;  
6.        a = b = c = 1;  
7.        return 0;  
8.    }  

gcc  -S  operator.c得到汇编代码(部分):
1.    movl    $1, -20(%rbp)  
2.    movl    -20(%rbp), %eax  
3.    movl    %eax, -16(%rbp)  
4.    movl    -16(%rbp), %eax  
5.    movl    %eax, -12(%rbp)  


-20(%rbp)即是表示c,-16(%rbp)表示b, -12(%rbp)表示a, 可以看到依次给c, b, a赋值,也就是体现了如下运算过程:
(a = (b = (c = 1)));
Q: 如下代码为什么结果始终不对?
1.    #include   
2.      
3.    int main()  
4.    {  
5.        int a = 2;  
6.        if(a & 1  == 0)  
7.            printf("a & 1 == 0");  
8.        else  
9.            printf("a & 1 != 0");  
10.      return 0;  
11.  }  


为什么一直输出“a & 1 != 0”  ?
A: 这是因为==的优先级高于表示位与运算符&.所以a & 1 == 0的实际代码是a & (1 == 0),也就是a & 0, 当然结果不是预期了。
可以看下它的汇编(部分):
1.    leaq    L_.str(%rip), %rcx  
2.    movq    %rcx, %rdi  
3.    callq   _printf  

1.    L_.str:  
2.        .asciz   "a & 1 != 0"  


可以看到编译器进行了优化,直接输出L_.str字符串,根本没有进行运行时再次运算if表达式的值。在这种情况下,需要注意是否忽略了运算符优先级导致编译器直接优化了。

Q: sizeof到底是个运算符还是关键字?
A: 它应该被看成运算符。下面是c标准内容:
1.    The sizeof operator yields the size (in bytes) of its operand, which may be an expression or the parenthesized name of a type. The size is determined from the type of the operand. The result is an integer. If the type of the operand is a variable length array type, the operand is evaluated; otherwise, the operand is not evaluated and the result is an integer constant.  


可以看出,sizeof被看成运算符。不过从另一个角度来说,关键字是从词法的角度进行分析的,运算符是从语法的角度分析的;如果从语法的角度来说,sizeof可以看成运算符,而从词法的角度来说,它不能看成关键字。不过依然不能把它当成变量来申明:
1.    #include   
2.      
3.    int main()  
4.    {  
5.        int sizeof = 1;  
6.        return 0;  
7.    }  


编译:
file:///C:/DOCUME~1/ADMINI~1/LOCALS~1/Temp/msohtml1/01/clip_image004.gif
可以看到编译出错了。
Q: 都说sizeof在编译期就可以计算出数值,怎么证明?
A:
1.    #include   
2.      
3.    int main()  
4.    {  
5.        int i = 1;  
6.        printf("%d\n", sizeof(i));  
7.        return 0;  
8.    }  


它对应的汇编代码为(部分):
1.                   movl $4, %eax  
2.    xorb    %cl, %cl  
3.    leaq    L_.str(%rip), %rdx  
4.    movq    %rdx, %rdi  
5.    movq    %rax, %rsi  
6.    movb    %cl, %al  
7.    callq   _printf  

笔者所在机器为x64架构,在x64上的参数传递,如果参数不超过6个,一般是先入寄存器,参数从左到右分别放入:rdi,rsi,rdx,rcx,r8,r9.
可以看到数字4直接放入了%eax, 最后用%eax的扩展寄存器%rax将数据放入%rsi; sizeof(i)的值在编译期就已经计算得到了4.
Q: sizeof后面可以跟表达式,下面的代码,为什么i++无效呢?
1.    #include   
2.      
3.    int main()  
4.    {  
5.        int i = 1;  
6.        sizeof(i++);  
7.        printf("%d\n", i);  
8.        return 0;  
9.    }  


运行结果:
file:///C:/DOCUME~1/ADMINI~1/LOCALS~1/Temp/msohtml1/01/clip_image006.gif
A: 正因为sizeof是编译期求值的,所以如果它跟着表达式,那么表达式是不被计算的,只是根本表达式的类型得到它占用的空间。看下它的汇编:
1.    movl    $1, -12(%rbp)  
2.    movl    -12(%rbp), %eax  
3.    xorb    %cl, %cl  
4.    leaq    L_.str(%rip), %rdx  
5.    movq    %rdx, %rdi  
6.    movl    %eax, %esi  
7.    movb    %cl, %al  
8.    callq   _printf  


可以看到,调用printf函数之前直接将数据1当做参数输出,至于sizeof(i++)中的i++根本没有对应指令。
Q: 对于普通数组arr来说,sizeof(arr)可以确定arr的大小,那变长数组的sizeof如何计算呢?
A: 正因为是可变数组,所以sizeof计算它大小的过程将被推迟到运行时。
1.    #include   
2.    #define PRINT_D(intValue)   printf(#intValue" is %lu\n", (intValue));  
3.      
4.    size_t  get_arr_len(int n)  
5.    {  
6.        char arr[n + 1];  
7.        return sizeof(arr);  
8.    }  
9.      
10.  int main()  
11.  {  
12.      PRINT_D(get_arr_len(1))  
13.      return 0;  
14.  }  

编译运行:

file:///C:/DOCUME~1/ADMINI~1/LOCALS~1/Temp/msohtml1/01/clip_image008.gif
这里先介绍下VLA(变长数组),它是c99引入的特性,表示一个数组的长度不一定在编译期就可以确定,可以推迟到运行时。
我们先看下上面代码的汇编(部分):
1.    0x0000000100000dcc :  add    $0x1,%edi  
2.    0x0000000100000dcf :  mov    %edi,%ecx  
3.    0x0000000100000dd1 :  mov    %rsp,%rdx  
4.    0x0000000100000dd4 :  mov    %rdx,-0x20(%rbp)  
5.    0x0000000100000dd8 :  mov    %rcx,-0x10(%rbp)  
6.    0x0000000100000ddc :  movl   $0x1,-0x24(%rbp)  
7.    0x0000000100000de3 :  mov    -0x20(%rbp),%rcx  
8.    0x0000000100000de7 :  mov    %rcx,%rsp  
9.    0x0000000100000dea :  mov    -0x10(%rbp),%rcx  
10.  0x0000000100000dee :  mov    (%rax),%rax  
11.  0x0000000100000df1 :  mov    -0x8(%rbp),%rdx  
12.  0x0000000100000df5 :  cmp    %rdx,%rax  
13.  0x0000000100000df8 :  mov    %rcx,-0x30(%rbp)  
14.  0x0000000100000dfc :  jne    0x100000e08   


可以看到,第一行执行了get_arr_len中n+1的操作,最后将得到的数据传递到%ecx中,最终在倒数第二行的地方将此数据传递出去;可以看出是在运行时计算数组大小的。
Q: 如下代码关于位运算符的操作为何最终结果和预期不符?
1.    #include   
2.      
3.    int main()  
4.    {  
5.        unsigned char c = 0xfc;  
6.        unsigned int i = ~c;  
7.        printf("%#x\n",i);  
8.        return 0;  
9.    }  


运行结果:
file:///C:/DOCUME~1/ADMINI~1/LOCALS~1/Temp/msohtml1/01/clip_image010.gif
按照上面的代码,~c应该得到的是0x03, 那么结果应该是0x03, 怎么会是上面图片的结果呢?
A: 这是因为位运算是被提升到整形运算的。上面的变量c是无符号字符型,在进行~位运算时,是首先提升为整形,即为0x000000fc, 然后取反得到0xffffff03, 所以i得到的数值是这个。同理,如果c是char类型,提升为整形时为0xfffffffc,再取反得到的就是0x03.其实变量被提升有很多地方,比如short计算时也会提升为int再继续计算。
xichen
2012-5-15 11:04:55


[ 本帖最后由 tiankai001 于 2012-12-15 07:40 编辑 ]
此帖出自单片机论坛
 
 
 

回复

6366

帖子

4929

TA的资源

版主

9
 
函数,它们的分工和人类的分工没什么不同----小话c语言(9)
[Mac-10.7.1Lion Intel-based x64 gcc4.2.1]
Q对于ctype.h中的isspaceisblank函数,一直没怎么分清楚,到底它们的不同在哪里?
A我们做个测试:
[cpp] view plaincopy
#include   
#include   
  
int main()
{  
   int i;  
   for(i = 0; i < 128; ++i)  
   {  
       if(isspace(i))  
           printf("%d is space\n", i);
       if(isblank(i))  
           printf("%d is blank\n", i);
   }  
   return 0;  
}  
编译运行:
可以看到ascii9, 10, 11, 12, 13, 32space;  9, 32blank.
查看isspaceisblank说明:
Q字符串转换成浮点数可以用atof, 那浮点数转换成字符串呢?
A可以使用sprintf函数组装字符串,也可以用gcvt函数实现。如下例子:
[cpp] view plaincopy
#include   
#define PRINT_D(intValue)   printf(#intValue" is %lu\n",(intValue));  
  
int main()
{  
   char buf[32];  
   sprintf(buf, "%lf", 2.345);
   printf("%s\n", buf);  
     
   return 0;  
}  
运行结果:
也可以用gcvt函数:
[cpp] view plaincopy
char *gcvt(double value, int ndigit, char*buf);  
其中ndigit是有效数字个数,buf为保存转换后的字符串;
如下例子:
[cpp] view plaincopy
#include   
#include   
#define PRINT_D(intValue)   printf(#intValue" is %lu\n",(intValue));  
  
int main()
{  
   char buf[32];  
   double d = 1.234567;  
   gcvt(d, 6, buf);  
   printf("%s\n", buf);  
     
   return 0;  
}  
运行结果:
另外,ecvt, fcvt也可以实现类似功能。
Q在申请内存的时候,经常申请ok了,然后调用memset将内存空间设置为0,有没有更简洁的形式?
A有的,calloc可以完成这个功能。
原型为:
[cpp] view plaincopy
void *calloc(size_t count, size_tsize);  
如下代码:
[cpp] view plaincopy
#include   
#include   
#define PRINT_D(intValue)   printf(#intValue" is %lu\n",(intValue));  
  
int main()
{  
   char *p = (char *)calloc(128, 1);
   if(p)  
   {  
       int i = 0;  
       for(; i < 128; ++i)  
        {  
           if(p != 0)  
                printf("the calloc memoryis not zero!\n");  
       }  
       free(p);  
   }  
     
   return 0;  
}  
当然,正常情况下,它什么也不会输出。
Q经常看到,申请一块缓存,大小为4KB,它就是系统的一页大小么?系统有相关的api么?
A有相关api.  getpagesize即可获取它的大小。
[cpp] view plaincopy
int getpagesize(void);  
如下例子:
[cpp] view plaincopy
#include   
#include   
#define PRINT_D(intValue)   printf(#intValue" is %d\n",(intValue));  
  
int main()
{  
   int page_size = getpagesize();  
   PRINT_D(page_size)  
     
   return 0;  
}  
运行结果:
Q经常听到内存映射,它的作用到底是什么?
A mmap函数就可以实现内存映射。它的用途之一就是当需要频繁操作文件的时候,可以将文件映射到内存中,在内存中读写这些可以提高效率。原型如下:
[cpp] view plaincopy
void *mmap(void *addr, size_t len, intprot, int flags, int fd, off_t offset);  
它需要头文件sys/mman.h的支持。
示例代码如下:
addr表示被映射的地址,可以传入NULL,返回值是系统分配的内存映射地址;
len表示文件多大的数据被映射到内存;
prot表示映射的读写或可执行方式,如PROT_READ是以读的方式;
flags表示映射的一些特性,比如MAP_SHARED表示允许其它映射此文件的进程共享;
fd表示映射的文件句柄;
offset表示文件映射的偏移量,必须是分页大小的整数倍;
现在写一个测试代码:
[cpp] view plaincopy
#include   
#include   
#include   
#include   
#include   
#include   
#define PRINT_D(intValue)   printf(#intValue" is %d\n",(intValue));  
  
int main()
{  
   int     ret;  
   char    *map_p;  
   struct  stat st;  
     
   long long     size =getpagesize();  
   // open file by read and write, the write property is used whenmodifying the mapped data  
   int     fd =open("test", O_RDWR);  
   if(fd < 0)  
   {  
       perror("open file error");
       return -1;  
   }  
   ret = fstat(fd, &st);  
   if(ret < 0)  
   {  
       perror("fstat file error");
       close(fd);  
       return -1;  
   }  
     
   // if the size is smaller than file's size, then use the file (size +1)  
   if(st.st_size >= size)  
       size = st.st_size + 1;  
     
   // map the file fd  
   map_p = (char *)mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd,0);  
   if(map_p == MAP_FAILED)  
   {  
       perror("mmap error");  
       close(fd);  
       return -1;  
   }  
   close(fd);  // when mapped ok, thefd can be closed.  
     
   printf("file content:%s\n", map_p);  
   // modify the first char  
   map_p[0] = 'a';  
     
   // sync the modify data  
   ret = msync(map_p, size, MS_SYNC);
   if(ret < 0)  
   {  
       perror("msync error");  
       munmap(map_p, size);  
       return -1;  
    }  
     
  
   printf("modify file content:%s\n", map_p);  
   // unmap  
   munmap(map_p, size);  
   return 0;  
}  
我们创建一个含有hello内容的文件:
-n参数表示不把最后的换行字符\n也写入文件;
ls -l | greptest命令确认文件内容为5字节:
可以看到确定是5字节。
然后运行上面的程序,观察控制台输出:
cat  test命令确认文件内容已被修改:
Q上面的代码中map_pchar *输出,结尾不是没设定\0么,怎么会正确结束?
A这是因为进行mmap内存映射的时候,会自动将映射后所有没有被映射的空间填充0,所以代码中没有显式设置。
Q关于时间的几个函数,感觉不容易分清楚,究竟如何理解它们?
A首先说下time函数:
[cpp] view plaincopy
time_t time(time_t *tloc);  
它返回的是197011日的UTC时间从000秒到现在的秒数。如果tloc非空,那么它也会存储在tloc对应的地址里。
time_t格式一般被定义为long或者unsignedlong这种格式。
一个例子:
[cpp] view plaincopy
#include   
#include   
  
#define PRINT_D(intValue)   printf(#intValue" is %d\n",(intValue));  
#define PRINT_L(longValue)  printf(#longValue" is %ld\n",(longValue));  
  
int main()
{  
   time_t  ret = time(NULL);  
   PRINT_L(ret)  
   return 0;  
}  
运行结果:
而这种格式的时间当然不直观,所以有localtime来转换:
[cpp] view plaincopy
struct tm *localtime(const time_t*clock);  
它返回一个tm结构的指针。可以利用time得到的返回值作为参数:
[cpp] view plaincopy
#include   
#include   
  
#define PRINT_D(intValue)   printf(#intValue" is %d\n", (intValue));  
#define PRINT_L(longValue)  printf(#longValue" is %ld\n",(longValue));  
  
int main()
{  
   time_t  ret = time(NULL);  
   struct tm *tm_ret;  
     
   PRINT_L(ret)  
     
   tm_ret = localtime(&ret);  
   PRINT_D(tm_ret->tm_year)  
   PRINT_D(tm_ret->tm_mon)  
   PRINT_D(tm_ret->tm_mday)  
   PRINT_D(tm_ret->tm_hour)  
   PRINT_D(tm_ret->tm_min)  
   PRINT_D(tm_ret->tm_sec)  
     
   return 0;  
}  
运行结果:
Q刚刚是time_t结构转换成struct tm结构,有相反的过程么?
A是的,可以使用mktime函数实现这个转换。
[cpp] view plaincopy
time_t mktime(struct tm *timeptr);  
就利用上面的代码:
[cpp] view plaincopy
#include   
#include   
  
#define PRINT_D(intValue)   printf(#intValue" is %d\n",(intValue));  
#define PRINT_L(longValue)  printf(#longValue" is %ld\n",(longValue));  
  
int main()
{  
   time_t  ret = time(NULL);  
   struct tm *tm_ret;  
     
   PRINT_L(ret)  
     
   tm_ret = localtime(&ret);  
   PRINT_D(tm_ret->tm_year)  
   PRINT_D(tm_ret->tm_mon)  
   PRINT_D(tm_ret->tm_mday)  
   PRINT_D(tm_ret->tm_hour)  
   PRINT_D(tm_ret->tm_min)  
   PRINT_D(tm_ret->tm_sec)  
     
   PRINT_L(mktime(tm_ret))  
     
   return 0;  
}  
运行结果:
可以看到,mktime返回的和程序开始time函数得到的time_t的值是一样的。
Q使用struct tm 结构来获取年月日时分秒,感觉有点复杂,有更简单的么?
A有的,可以使用ctimeasctime来得到时间相关的char *字符串,这个要更简洁一点。
[cpp] view plaincopy
char *ctime(const time_t *clock);  
char *asctime(const struct tm*timeptr);  
可以看到,这两个函数有不同之处,参数不同。示例代码:
[cpp] view plaincopy
#include   
#include   
  
#define PRINT_D(intValue)   printf(#intValue" is %d\n",(intValue));  
#define PRINT_L(longValue)  printf(#longValue" is %ld\n",(longValue));  
#define PRINT_STR(str)      printf(#str" is %s\n",(str));  
  
int main()
{  
   time_t  ret = time(NULL);  
   struct tm *tm_ret;  
     
   PRINT_L(ret)  
     
   tm_ret = localtime(&ret);  
   PRINT_D(tm_ret->tm_year)  
   PRINT_D(tm_ret->tm_mon)  
   PRINT_D(tm_ret->tm_mday)  
   PRINT_D(tm_ret->tm_hour)  
   PRINT_D(tm_ret->tm_min)  
   PRINT_D(tm_ret->tm_sec)  
     
   PRINT_L(mktime(tm_ret))  
     
   PRINT_STR(ctime(&ret))  
   PRINT_STR(asctime(tm_ret))  
     
   return 0;  
}  
运行结果:
Q关于内存拷贝,memcpymemmove到底有什么区别?
A区别在于memmove处理了重复内存区域的拷贝,memcpy没有处理内存区域重复可能导致拷贝结果错误,这种情况下结果是不确定的。所以最好使用memmove,如果可以确定内存区域不重复,也可以直接使用memcpy.
Q关于系统信息,如何获取系统的用户组信息?
A getgrent函数可以实现。
group结构如下:
[cpp] view plaincopy
struct group {  
   char    *gr_name;       /* [XBD] group name */  
   char    *gr_passwd;     /* [???] group password */  
   gid_t   gr_gid;         /* [XBD] group id */  
   char    **gr_mem;       /* [XBD] group members */  
};  
示例代码:
[cpp] view plaincopy
#include   
#include   
#include   
#include   
  
#define PRINT_D(intValue)   printf(#intValue" is %d\n",(intValue));  
#define PRINT_L(longValue)  printf(#longValue" is %ld\n",(longValue));  
#define PRINT_STR(str)      printf(#str" is %s\n",(str));  
  
int main()
{  
   struct group *data;  
   while(data = getgrent())  
   {  
       printf("%s:%s:%d\n", data->gr_name, data->gr_passwd,data->gr_gid);  
   }  
   endgrent();  
   return 0;  
}  
运行结果(不同系统的结果会不同)
[plain] view plaincopy
_amavisd:*:83  
_appowner:*:87  
_appserveradm:*:81  
_appserverusr:*:79  
_appstore:*:33  
_ard:*:67
_atsserver:*:97  
_calendar:*:93  
_carddav:*:206  
_ces:*:32
_clamav:*:82  
_coreaudiod:*:202  
_cvms:*:212  
_cvs:*:72
_detachedsig:*:207  
_devdocs:*:59  
_developer:*:204  
_devicemgr:*:220  
_dovenull:*:227  
_dpaudio:*:215  
_guest:*:201  
_installassistant:*:25  
_installer:*:96  
_jabber:*:84  
_keytabusers:*:30  
_lda:*:211
_locationd:*:205  
_lp:*:26
_lpadmin:*:98  
_lpoperator:*:100  
_mailman:*:78  
_mcxalr:*:54  
_mdnsresponder:*:65  
_mysql:*:74
_netbios:*:222  
_netstatistics:*:228  
_networkd:*:24  
_odchpass:*:209  
_pcastagent:*:55  
_pcastfeedadmins:*:223  
_pcastlibrary:*:225  
_pcastserver:*:56  
_podcastlibraryfeedadmins:*:226  
_postdrop:*:28  
_postfix:*:27  
_postgres:*:216  
_qtss:*:76
_sandbox:*:60  
_screensaver:*:203  
_scsd:*:31
_securityagent:*:92  
_serialnumberd:*:58  
_softwareupdate:*:200  
_spotlight:*:89  
_sshd:*:75
_svn:*:73
_teamsserver:*:94  
_timezone:*:210  
_tokend:*:91  
_trustevaluationagent:*:208  
_unknown:*:99  
_update_sharing:*:95  
_usbmuxd:*:213  
_uucp:*:66
_warmd:*:224  
_webauthserver:*:221  
_windowserver:*:88  
_www:*:70
_xgridagent:*:86  
_xgridcontroller:*:85  
access_bpf::501  
accessibility:*:90  
admin:*:80
authedusers:*:50  
bin:*:7
certusers:*:29  
com.apple.access_screensharing-disabled::101  
com.apple.access_screensharing::401  
com.apple.sharepoint.group.1::402  
com.apple.sharepoint.group.2::403  
consoleusers:*:53  
daemon:*:1
dialer:*:68
everyone:*:12  
group:*:16
interactusers:*:51  
kmem:*:2
localaccounts:*:61  
macports::502  
mail:*:6
messagebus:*:500  
netaccounts:*:62  
netusers:*:52  
network:*:69  
nobody:*:-2
nogroup:*:-1  
operator:*:5  
owner:*:10
procmod:*:9
procview:*:8  
smmsp:*:102
staff:*:20
sys:*:3
tty:*:4
utmp:*:45
wheel:*:0
nobody:*:-2
nogroup:*:-1  
wheel:*:0
daemon:*:1
kmem:*:2
sys:*:3
tty:*:4
operator:*:5  
mail:*:6
bin:*:7
procview:*:8  
procmod:*:9
owner:*:10
everyone:*:12  
group:*:16
staff:*:20
_networkd:*:24  
_installassistant:*:25  
_lp:*:26
_postfix:*:27  
_postdrop:*:28  
certusers:*:29  
_keytabusers:*:30  
_scsd:*:31
_ces:*:32
_appstore:*:33  
utmp:*:45
authedusers:*:50  
interactusers:*:51  
netusers:*:52  
consoleusers:*:53  
_mcxalr:*:54  
_pcastagent:*:55  
_pcastserver:*:56  
_serialnumberd:*:58  
_devdocs:*:59  
_sandbox:*:60  
localaccounts:*:61  
netaccounts:*:62  
_mdnsresponder:*:65  
_uucp:*:66
_ard:*:67
dialer:*:68
network:*:69  
_www:*:70
_cvs:*:72
_svn:*:73
_mysql:*:74
_sshd:*:75
_qtss:*:76
_mailman:*:78  
_appserverusr:*:79  
admin:*:80
_appserveradm:*:81  
_clamav:*:82  
_amavisd:*:83  
_jabber:*:84  
_xgridcontroller:*:85  
_xgridagent:*:86  
_appowner:*:87  
_windowserver:*:88  
_spotlight:*:89  
accessibility:*:90  
_tokend:*:91  
_securityagent:*:92  
_calendar:*:93  
_teamsserver:*:94  
_update_sharing:*:95  
_installer:*:96  
_atsserver:*:97  
_lpadmin:*:98  
_unknown:*:99  
_lpoperator:*:100  
_softwareupdate:*:200  
_guest:*:201  
_coreaudiod:*:202  
_screensaver:*:203  
_developer:*:204  
_locationd:*:205  
_detachedsig:*:207  
_trustevaluationagent:*:208  
_odchpass:*:209  
_timezone:*:210  
_lda:*:211
_cvms:*:212
_usbmuxd:*:213  
_postgres:*:216  
_devicemgr:*:220  
_webauthserver:*:221  
_netbios:*:222  
_pcastfeedadmins:*:223  
_warmd:*:224  
_pcastlibrary:*:225  
_podcastlibraryfeedadmins:*:226  
_dovenull:*:227  
_netstatistics:*:228  
同时,getgrgid函数可以指定gid得到组信息。
Q获取用户信息呢?
A可以使用getuid或者geteuid得到需要的用户ID
[cpp] view plaincopy
#include   
#include   
#include   
#include   
#include   
  
#define PRINT_D(intValue)   printf(#intValue" is %d\n",(intValue));  
#define PRINT_UD(intValue)  printf(#intValue" is %u\n",(intValue));  
#define PRINT_L(longValue)  printf(#longValue" is %ld\n",(longValue));  
#define PRINT_STR(str)      printf(#str" is %s\n",(str));  
  
int main()
{  
   PRINT_UD(getuid())  
   PRINT_UD(geteuid())  
   return 0;  
}  
运行结果(依赖于系统)
笔者当时系统的用户为xichen,使用id  xichen得到此用户的信息:
可以看到uid确实是501.至于uideuid的区别,这里就不做分析了。同理,如果要获取组id, 可以使用getgidgetegid函数。
Q需要获取密码信息,如何得到?
A可以使用getpwent来获得。
[cpp] view plaincopy
struct passwd *getpwent(void);  
它的使用类似getgrent函数。
Q关于文件操作,creat函数和open函数有什么区别?
A注意,它不是create,creat.  creat相当于特殊的open函数,它和open的关系如下:
如下示例代码:
[cpp] view plaincopy
#include   
#include   
#include   
#include   
#include   
#include   
#include   
#include   
  
#define PRINT_D(intValue)   printf(#intValue" is %d\n",(intValue));  
#define PRINT_UD(intValue)  printf(#intValue" is %u\n",(intValue));  
#define PRINT_L(longValue)  printf(#longValue" is %ld\n",(longValue));  
#define PRINT_STR(str)      printf(#str" is %s\n",(str));  
  
int main()
{  
   int ret = creat("test", S_IRUSR);  
   if(ret < 0)  
   {  
       perror("creat file error");
       return -1;  
   }  
   return 0;  
}  
执行后,它会会得到一个空文件test.
Q关于文件操作,dupdup2到底有什么作用?
A原型如下:
[cpp] view plaincopy
int dup(int fildes);  
int dup2(int fildes, int fildes2);  
dup可以复制给定的文件描述符,返回新的文件描述符,两个文件描述符共享相同的系统内部数据结构;
dup2可以将第一个参数指定的文件描述符的信息恢复到第二个参数指定的文件描述符,关于基本输入输出那篇文章最后已经使用了dup2进行恢复,这里不做介绍。
[cpp] view plaincopy
#include   
#include   
#define PRINT_D(intValue)   printf(#intValue" is %d\n",(intValue));  
  
int main()
{  
   int fd = dup(fileno(stdout));  
   FILE *file;  
   PRINT_D(fd)  
     
   // use stdout to output  
   fprintf(stdout, "uses fd:%d output\n", fileno(stdout));  
     
   // get the FILE * by file descriptor
   file = fdopen(fd, "w");
   if(!file)  
   {  
       perror("fdopen error");
       close(fd);  
       return -1;  
   }  
     
   // use new fd to output  
   fprintf(file, "uses fd:%d output\n", fd);  
     
   fclose(file);  
   return 0;  
}  
可以看到,dup产生的新文件描述符值为3,也可以和stdout(文件描述符对应于1)一样进行标准输出。
Q fflushfsync到底有什么区别?
A fflush只是将数据写到内核缓冲区,并不一定写到设备上;fsync会将内核缓冲区中需要被写到设备上的数据写到设备上;
Q有的时候需要生成一个临时文件,自己去创建可能重复,可能覆盖已有的文件,想要生成一个随机的文件,有相关函数么?
A是的。mkstemp函数可以完成此功能。
[cpp] view plaincopy
int mkstemp(char *template);  
示例代码:
[cpp] view plaincopy
#include   
#include   
#define PRINT_D(intValue)   printf(#intValue" is %d\n",(intValue));  
#define PRINT_STR(str)      printf(#str" is %s\n",(str));  
  
#define ERR_PERROR_RETURN(str) \  
       if(ret < 0)     \  
       {       \  
           perror(#str" error");  \  
           return -1;              \  
       }     
  
int main()
{  
   // mkstemp's first argument should has postfix XXXXXX, or the resultwill be not what you need.  
   char buf[] = "hello-XXXXXX";
   int ret = mkstemp(buf);  
   ERR_PERROR_RETURN(mkstemp)  
   PRINT_STR(buf)  
     
   return 0;  
}  
运行结果:
ls -l命令查看是否创建了上面的文件:
可以看到,确实创建了这样的文件。
Q文件操作函数clearerr函数该在何时使用?
A当操作文件后出现了错误但是又需要继续操作时可以用clearerr清除错误标志位,以避免后面的文件操作因为ferror的判断而导致错误。
[cpp] view plaincopy
#include   
#include   
#define PRINT_D(intValue)   printf(#intValue" is %d\n",(intValue));  
#define PRINT_STR(str)      printf(#str" is %s\n",(str));  
  
#define ERR_PERROR_RETURN(str) \  
       if(ret < 0)     \  
       {       \  
           perror(#str" error");  \  
           return -1;              \  
       }     
  
int main()
{  
   FILE *fp = fopen("test", "r");  
   if(fp)  
   {  
       int ch = 'a';  
       fputc(ch, fp);  
       if(ferror(fp))  
       {  
           printf("ferror...\n");  
       }  
       ch = fgetc(fp);  
       printf("%c\n", ch);  
       PRINT_D(ferror(fp))  
         
       fclose(fp);  
   }  
     
   return 0;  
}  
运行:
可以看到出现ferror后,没有清除错误标志位,后面判断ferror依然为错误。所以,可以增加clearerr函数。
[cpp] view plaincopy
#include   
#include   
#define PRINT_D(intValue)   printf(#intValue" is %d\n",(intValue));  
#define PRINT_STR(str)      printf(#str" is %s\n",(str));  
  
#define ERR_PERROR_RETURN(str) \  
       if(ret < 0)     \  
       {       \  
           perror(#str" error");   \  
           return -1;              \  
       }     
  
int main()
{  
   FILE *fp = fopen("test", "r");  
   if(fp)  
   {  
       int ch = 'a';  
       fputc(ch, fp);  
       if(ferror(fp))  
       {  
           printf("ferror...\n");  
           clearerr(fp);  
       }  
       ch = fgetc(fp);  
       printf("%c\n", ch);  
       PRINT_D(ferror(fp))  
         
       fclose(fp);  
   }  
     
   return 0;  
}  
运行:
发现最后ferror判断已经正常了。
Q关于文件缓冲区操作,有setbuf, setbuffer,setvbuf, setlinebuf, 它们究竟什么关系?
A现依依介绍。下面先举个关于setvbuf的例子:
[cpp] view plaincopy
int setvbuf(FILE *restrict stream, char *restrict buf, int type, size_tsize);  
第一个参数为文件指针;第二个参数为buf地址;第三个参数为缓冲区类型,有无缓冲,行缓冲和全缓冲3中方式;第四个参数为缓冲区大小。
[cpp] view plaincopy
#include   
#include   
#define PRINT_D(intValue)   printf(#intValue" is %d\n",(intValue));  
#define PRINT_STR(str)      printf(#str" is %s\n",(str));   
  
#define PAUSE       { getc(stdin);    rewind(stdin); }  
  
static char    buf[BUFSIZ];  
  
int main()
{  
   setvbuf(stdout, buf, _IOFBF, 6);  
   printf("hello");  
   PAUSE  
     
   return 0;  
}  
如上,设置标准输出的缓冲区为buf,且大小为6 采用全缓冲;所以后面执行的时候,输出hello,它是5个字节,没有达到缓冲区大小6,所以不会立即输出;执行到PAUSE,任意输入一个字符,hello会被输出。
执行后,随意输入一个字符,比如上面的a,然后换行,hello才被显示。
如果将代码setvbuf最后的参数改为5,那么hello会在程序运行后便输出。
[cpp] view plaincopy
#include   
#include   
#define PRINT_D(intValue)   printf(#intValue" is %d\n",(intValue));  
#define PRINT_STR(str)      printf(#str" is %s\n",(str));   
  
#define PAUSE       { getc(stdin);    rewind(stdin); }  
  
static char    buf[BUFSIZ];  
  
int main()
{  
   setvbuf(stdout, buf, _IOFBF, 5);  
   printf("hello");  
   PAUSE  
     
   return 0;  
}  
运行:
接着介绍setbuf函数:
[cpp] view plaincopy
void setbuf(FILE *restrict stream, char *restrict buf);  
它实际上就等同于如下代码:
接下来是setlinebuf函数:
[cpp] view plaincopy
int setlinebuf(FILE *stream);  
它等同于:
不过它的缓冲区大小取决于调用者。
setbuffer函数:
[cpp] view plaincopy
void setbuffer(FILE *stream, char *buf, int size);  
类似的,下面为测试代码:
[cpp] view plaincopy
#include   
#include   
#define PRINT_D(intValue)   printf(#intValue" is %d\n",(intValue));  
#define PRINT_STR(str)      printf(#str" is %s\n", (str));   
  
#define PAUSE       { getc(stdin);    rewind(stdin); }  
  
static char    buf[BUFSIZ];  
  
int main()
{  
   setbuffer(stdout, buf, 5);  
   printf("hello");  
     
   PAUSE  
     
   return 0;  
}  
运行结果就不写了。
Q关于进程控制的函数exit_exit到底有什么区别?
A区别在于_exit退出进程前不会刷新缓冲区、关闭流等操作,而exit函数会。如下代码(文件名为testForC.c)
[cpp] view plaincopy
#include   
#include   
#include   
#define PRINT_D(intValue)   printf(#intValue" is %d\n",(intValue));  
#define PRINT_STR(str)      printf(#str" is %s\n",(str));   
  
int main()
{  
   printf("hello");  
   exit(0);  
}  
运行:
如果使用_exit函数:
[cpp] view plaincopy
#include   
#include   
#include   
#define PRINT_D(intValue)   printf(#intValue" is %d\n",(intValue));  
#define PRINT_STR(str)      printf(#str" is %s\n",(str));   
  
int main()
{  
   printf("hello");  
   _exit(0);  
}  
运行:
可以发现运行它并没有输出缓冲区中的hello.
Q如何获取进程的优先级?
A使用getpriority即可。
[cpp] view plaincopy
#include   
#include   
#include   
#define PRINT_D(intValue)   printf(#intValue" is %d\n",(intValue));  
#define PRINT_STR(str)      printf(#str" is %s\n",(str));   
#define PAUSE       { getc(stdin);    rewind(stdin); }  
  
int main()
{  
    pid_t   pid = getpid();  
   int     priority =getpriority(PRIO_PROCESS, pid);  
   PRINT_D(priority)  
   PAUSE  
     
   return 0;  
}  
运行后:
使用如下命令得到它的优先级:ps-l
可以看到testForC进程的优先级列NI也为0.和上面得到的是一致的。
如果要修改优先级,可以使用setpriority函数或者nice函数。
xichen
2012-5-16 17:53:01


[ 本帖最后由 tiankai001 于 2012-12-15 07:41 编辑 ]
此帖出自单片机论坛
 
 
 

回复

6366

帖子

4929

TA的资源

版主

10
 
递归,到处都是递归----小话c语言(10)
[Mac10.7.1  Lion  Intel-based  x64  gcc4.2.1]
Q: 递归的本质是什么?
A: 递归能够运行,在于宇宙万物之间的联系和计算机最终指令的完备性。换句话说,如果一个公式的参数不能和参数相关的公式有联系,那么递归对它无解。
Q: 举些例子说明递归的无处不在吧。
A: 比如需要计算一个数组中最大的数值。
1.     int find_max(int arr[], int size)  

如果从递归的角度,求一个数组中最大的值被分解为求第一个数和剩下一个数组中最大值比较得到的最大值。

所以代码如下:
1.     #include   
2.      
3.     #define PRINT_D(intValue)   printf(#intValue" is %d\n", (intValue));  
4.     #define MAX(a, b)   ((a) > (b) ? (a) : (b))  
5.      
6.     int find_max(int arr[], int size)  
7.     {  
8.         if(size == 1)  
9.             return arr[0];  
10.      if(size == 2)  
11.          return MAX(arr[0], arr[1]);  
12.      return MAX(arr[0], find_max(arr + 1, size - 1));  
13.  }  
14.   
15.  int main()  
16.  {  
17.      int arr[] = {34, 2, 190, 345, 23};  
18.      int max_ret = find_max(arr, sizeof(arr) / sizeof(arr[0]));  
19.      PRINT_D(max_ret)  
20.        
21.      return 0;  
22.  }  

运行结果:

file:///C:/DOCUME~1/ADMINI~1/LOCALS~1/Temp/msohtml1/01/clip_image002.gif
同理,求数组中最小值,求字符串的长度,数组中所有元素的和等等也类似。
Q: 字符串反转可以使用递归么?
A: 是的。如果需要将一个字符串反转,可以这么思考:它将首先将第一个和最后一个字符反转,接着将中间的字符串反转,代码如下:
1.     #include   
2.      
3.     #define PRINT_STR(str)      printf(#str" is %s\n", (str));  
4.     #define SWAP(type, a, b)    {type temp = a; a = b; b = temp;}  
5.      
6.     void    reverse_str(char *str, int len)  
7.     {  
8.         if(len <= 1)  
9.             return;  
10.      SWAP(char, str[0], str[len - 1]);  
11.      reverse_str(str + 1, len - 2);  
12.  }  
13.   
14.  int main()  
15.  {  
16.      char buf[] = "hello world";  
17.      reverse_str(buf, strlen(buf));  
18.      PRINT_STR(buf)  
19.      return 0;  
20.  }  

运行结果:

file:///C:/DOCUME~1/ADMINI~1/LOCALS~1/Temp/msohtml1/01/clip_image004.gif
Q: 将一个整形数的字符串形式输出,如何用递归?
A: 可以将此整数模10,得到的数据最后输出;继续递归整数除以10剩下的数值。
1.     #include   
2.      
3.     void    print_int(int n)  
4.     {  
5.         if(n >= 10)  
6.             print_int(n / 10);  
7.         printf("%d", n % 10);  
8.     }  
9.      
10.  int main()  
11.  {  
12.      print_int(123);  
13.      return 0;  
14.  }  

执行结果:

file:///C:/DOCUME~1/ADMINI~1/LOCALS~1/Temp/msohtml1/01/clip_image006.gif
同理,逆序输出,判断是否是回文也是类似的。
Q: 输出一个整形对应二进制形式中含有位1的个数,如何用递归?
A: 它的值可以看成:如果整数n是奇数,那么返回 n/ 2二进制含有位1个数 +1的和;如果是偶数,直接返回 n/ 2二进制含有位1的个数;
代码如下:
1.     #include   
2.     #define PRINT_D(intValue)   printf(#intValue" is %d\n", (intValue));  
3.      
4.     int int_has_1_count(int n)  
5.     {  
6.         if(n <= 1)  
7.             return n % 2;  
8.         if(n % 2 == 0)  
9.             return int_has_1_count(n / 2);  
10.      else  
11.          return int_has_1_count(n / 2) + 1;  
12.  }  
13.   
14.  int main()  
15.  {  
16.      PRINT_D(int_has_1_count(1099))  
17.      return 0;  
18.  }  

运行结果:

file:///C:/DOCUME~1/ADMINI~1/LOCALS~1/Temp/msohtml1/01/clip_image008.gif
1099的二进制形式是:10001001011, 可以确定含有二进制位1的个数为5.
同理,二分查找,二叉树遍历等都类似。
Q: 求一个数加上另一个数m的值,如何用递归?
A: 将此过程分解为m次自增操作。如下示例:
1.     #include   
2.     #define PRINT_D(intValue)   printf(#intValue" is %d\n", (intValue));  
3.      
4.     int add_x(int *n, int add_num)  
5.     {  
6.         if(add_num < 1)  
7.             return *n;  
8.         if(add_num == 1)  
9.             return ++(*n);  
10.      else  
11.      {  
12.          ++(*n);  
13.          return add_x(n, add_num - 1);  
14.      }  
15.  }  
16.   
17.  int main()  
18.  {  
19.      int n = 12;  
20.      PRINT_D(add_x(&n, 3))  
21.      return 0;  
22.  }  

运行结果:

file:///C:/DOCUME~1/ADMINI~1/LOCALS~1/Temp/msohtml1/01/clip_image010.gif
类似,两个变量之间的操作都可以转换成更小的操作,看怎么使用更小的操作来实现大操作。
Q: 3个不同元素abc,输出排列方式组成的所有形式。比如,abc, acb, bca等等。
A: 将输出的大过程看成需要输出此3个元素,小过程为输出一个元素,且只能输出一次。代码如下:
1.     #include   
2.     #define PRINT_D(intValue)   printf(#intValue" is %d\n", (intValue));  
3.      
4.     // a, b, c : the output char;  
5.     // print_count_a, print_count_b, print_count_c : can print count  
6.     // output: the output array  
7.     // output_index: current print index  
8.     void    print_abc_all_styles(char a, char b, char c,   
9.                                  int print_count_a,   
10.                               int print_count_b,  
11.                               int print_count_c,  
12.                               char output[],  
13.                               int  output_index)  
14.  {  
15.      if(print_count_a + print_count_b + print_count_c == 0)  
16.      {  
17.          printf("%c%c%c", output[0], output[1], output[2]);  
18.          printf("\n");  
19.          return;  
20.      }  
21.        
22.      if(print_count_a == 1)  
23.      {  
24.          output[output_index] = a;  
25.          --print_count_a;  
26.          print_abc_all_styles(a, b, c,   
27.                               print_count_a,   
28.                               print_count_b,   
29.                               print_count_c,  
30.                               output, output_index + 1);  
31.          ++print_count_a;  
32.            
33.      }  
34.      if(print_count_b == 1)  
35.      {  
36.          output[output_index] = b;  
37.          --print_count_b;  
38.          print_abc_all_styles(a, b, c,   
39.                               print_count_a,   
40.                               print_count_b,   
41.                               print_count_c,  
42.                               output, output_index + 1);  
43.          ++print_count_b;  
44.      }  
45.      if(print_count_c == 1)  
46.      {  
47.          output[output_index] = c;  
48.          --print_count_c;  
49.          print_abc_all_styles(a, b, c,   
50.                               print_count_a,   
51.                               print_count_b,   
52.                               print_count_c,  
53.                               output, output_index + 1);  
54.          ++print_count_c;  
55.      }  
56.  }  

运行结果:

file:///C:/DOCUME~1/ADMINI~1/LOCALS~1/Temp/msohtml1/01/clip_image012.gif
对于abc这样的排列,或者aabc,abbc, aabbcc这样的排列或者组合,都可以用类似方法解决;
一个更一般的代码:
1.     #include   
2.     #include   
3.     #include   
4.     #define PRINT_D(intValue)   printf(#intValue" is %d\n", (intValue));  
5.      
6.     static  bool    isExist(const char *str, const int str_size, char ch)  
7.     {  
8.         int i;  
9.         for(i = 0; i < str_size; ++i)  
10.      {  
11.          if(str == ch)  
12.              return true;  
13.      }  
14.      return false;  
15.  }  
16.   
17.  void    print_abc_all_styles(const char *elements,  // be outputed elements  
18.                               int max_print_count,   // max printed elements  
19.                               char output[],     // output string  
20.                               const int  output_size,  // output string size  
21.                               int  output_index) // current output index  
22.  {  
23.      int i;  
24.      if(max_print_count == 0)  
25.      {  
26.          int i = 0;  
27.          for(; i < output_size; ++i)  
28.              printf("%c", output);  
29.          printf("\n");  
30.          return;  
31.      }  
32.        
33.      for(i = 0; i < strlen(elements); ++i)  
34.      {  
35.          if(!isExist(output, output_index, elements))  
36.          {  
37.              output[output_index] = elements;  
38.              --max_print_count;  
39.              print_abc_all_styles(elements, max_print_count,  
40.                                   output, output_size, output_index + 1);  
41.              ++max_print_count;  
42.          }  
43.      }  
44.  }  
45.   
46.  int main()  
47.  {  
48.      char buf[4] = {0};  
49.      print_abc_all_styles("abc", 3, buf, 3, 0);  
50.      return 0;  
51.  }  

Q: 如何输出一段字符串?
A: 先输出第一个,然后将剩下的当做字符串输出;
Q: 如何下载文件?
A: 先下载前1个字节,后面继续使用前面的方式下载;
Q: 如何飞到月球?
A: 先飞第一段,然后飞剩下所有段;
Q: 如何做一盘菜?
A: 先做菜的第一部分,然后做剩下的部分;
Q: 如何追小妞?
A: 第一天采用某种方法和她相遇搭讪,后来采用类似但可能不同的方式找她搭讪,做该做的事,最后搞定她。不过涉及到情感问题,不能像计算机一样,它是不稳定的。
Q: 如何回家?
A: 做一件离你家更近的事情,然后继续做类似的事情;
Q: 如何上网?
A: 先做上网的第一个准备工作,然后做剩下的工作;
Q: 如何吃饭?
A: 先吃第一口,之后吃完所有该吃的饭。
Q: 如何写代码?
A: 先写第一句,后面写完所有的代码;
Q:.........
A:.........
总之,不要认为回答的太简单,而在于递归本来就很简单。
xichen
2012-5-1711:50:49


[ 本帖最后由 tiankai001 于 2012-12-15 07:42 编辑 ]
此帖出自单片机论坛
 
 
 

回复

6366

帖子

4929

TA的资源

版主

11
 
结构体,面向对象的基础----小话c语言(11)
[Mac-10.7.1 Lion  Intel-based  x64 gcc4.2.1]
Q结构体的本质是什么?
A结构体就像一种粘合剂,将事物之间的关系很好地组合在了一起。
Q结构体对象中各个变量的内存存储位置和内置基本类型变量的存储有什么区别?
A简单地说,它们没区别;复杂地说,它们有区别。简单在于它们终究会存储在内存中,复杂地说它们的位置可能有一些不同。如下:
[cpp] view plaincopy
#include   
#include   
  
#define PRINT_D(intValue)   printf(#intValue" is %d\n",(intValue));  
  
typedef struct  
{  
   char    sex;  
   int     age;  
}student;
  
// print every byte of the obj  
void   print_struct(void   *obj, intsize)  
{  
   int i;  
   unsigned char *temp = (unsigned char *)obj;  
   for (i = 0; i < size; ++i)  
   {  
       printf("%#x ", temp);
   }  
   printf("\n");  
}  
  
int main()
{  
   student s;  
   s.sex = 'm';  
   s.age = 25;  
   print_struct(&s, sizeof(student));
     
   return 0;  
}  
运行结果:
可以看到s对象的sex成员值为'm',对应于0x6d, sage成员值对应于0x19.因为笔者的机器为小端法,所以它们都在低地址显示,高地址补零。这里也可以看到student结构中的sex成员虽然是char类型,却依然占用了4字节,这是被补齐了。
Q如何获取结构体中一个成员的偏移位置呢?
A
[cpp] view plaincopy
#include   
#include   
  
#define PRINT_D(intValue)   printf(#intValue" is %d\n",(intValue));  
typedef struct  
{  
   char    sex;  
   int     age;  
}student;
  
int main()
{  
   student s;  
   PRINT_D((char *)&s.sex - (char *)&s)  
   PRINT_D((char *)&s.age - (char *)&s)  
     
   return 0;  
}  
如上代码,定义一个student类型的对象s,输出ssex成员的地址和s地址的差即为偏移,同理age的偏移也可以获得。
Q有更一般的方法么,不用创建对象,直接获得一个结构体某个成员的偏移位置?
A有的。更一般就意味着更抽象。将上面的过程抽象,取一个特定结构体对象的某个成员的偏移。
[cpp] view plaincopy
#include   
#include   
  
#define PRINT_D(intValue)   printf(#intValue" is %d\n",(intValue));  
typedef struct  
{  
   char    sex;  
   int     age;  
}student;
  
int main()
{  
   PRINT_D(&((student *)0)->sex - (char *)0)  
   PRINT_D((char *)&((student *)0)->age - (char *)0)  
   return 0;  
}  
上面的代码,在地址0抽象出了一个student类型的指针,然后获得各个成员的偏移;
运行结果:
Q上面的格式看起来有点复杂,有更简单的封装么?
A我们可以采用宏定义:
[cpp] view plaincopy
#define OFFSET(struct, member)  ((char *)&((struct *)0)->member -(char *)0)  
如下代码:
[cpp] view plaincopy
#include   
#include   
  
#define PRINT_D(intValue)   printf(#intValue" is %d\n",(intValue));  
#define OFFSET(struct, member)  ((char *)&((struct *)0)->member -(char *)0)  
  
typedef struct  
{  
   char    sex;  
   int     age;  
}student;
  
int main()
{  
   PRINT_D(OFFSET(student, sex))  
   PRINT_D(OFFSET(student, age))  
   return 0;  
}  
输出结果:
当然,也可以使用系统头文件中定义的宏offsetof, 示例代码如下:
[cpp] view plaincopy
#include   
#include   
#include   
  
#define PRINT_LUD(intValue)     printf(#intValue" is %lu\n",(intValue));  
#define OFFSET(struct, member)  ((char *)&((struct *)0)->member -(char *)0)  
  
typedef struct  
{  
   char    sex;  
   int     age;  
}student;
  
int main()
{  
   PRINT_LUD(offsetof(student, sex))
   PRINT_LUD(offsetof(student, age))
   return 0;  
}  
Q上面代码中的OFFSET宏,用地址0处的指针访问成员,难道不会出现访问违例?
A先写如下代码:
[cpp] view plaincopy
#include   
#include   
  
#define PRINT_D(intValue)     printf(#intValue" is %d\n",(intValue));  
#define OFFSET(struct, member)  ((char *)&((struct *)0)->member -(char *)0)  
  
typedef struct  
{  
   char    sex;  
   int     age;  
}student;
  
int main()
{  
   student *s = (student *)0;  
   PRINT_D(s->age)  
   return 0;  
}  
代码试图访问地址0处的数据,运行:
可以看到出现访问违例。那么OFFSET宏是如何正确执行的呢?我们查看下使用OFFSET的代码的汇编:
[cpp] view plaincopy
#include   
#include   
  
#define PRINT_D(intValue)     printf(#intValue" is %d\n",(intValue));  
#define OFFSET(struct, member)  ((char *)&((struct *)0)->member -(char *)0)  
  
typedef struct  
{  
   char    sex;  
   int     age;  
}student;
  
int main()
{  
   int ret = OFFSET(student, age);  
   return 0;  
}  
main函数的汇编代码:
[cpp] view plaincopy
0x00001f70 :  push  %ebp  
0x00001f71 :  mov   %esp,%ebp  
0x00001f73 :  sub   $0x8,%esp  
0x00001f76 :  mov   $0x0,%eax  
0x00001f7b : mov    %eax,%ecx
0x00001f7d : add    $0x4,%ecx
0x00001f80 : movl   $0x0,-0x4(%ebp)  
0x00001f87 : mov    %ecx,-0x8(%ebp)  
0x00001f8a : add    $0x8,%esp
0x00001f8d : pop    %ebp
0x00001f8e : ret      
可以看到,第五行%ecx0, add $0x4,%ecx%ecx4,得到4,然后将这个数值放到ret变量的位置.可见,编译器已经进行了优化。换个更简单的代码:
[cpp] view plaincopy
#include   
#include   
  
#define PRINT_D(intValue)     printf(#intValue" is %d\n",(intValue));  
#define OFFSET(struct, member)  ((char *)&((struct *)0)->member -(char *)0)  
  
typedef struct  
{  
   char    sex;  
   int     age;  
}student;
  
int main()
{  
   void *p = &((student *)0)->age;
   return 0;  
}  
上面的代码仅仅是获取地址为0student指针的age成员的地址,汇编如下:
[cpp] view plaincopy
0x00001f70 :  push  %ebp  
0x00001f71 :  mov   %esp,%ebp  
0x00001f73 :  sub   $0x8,%esp  
0x00001f76 :  mov   $0x0,%eax  
0x00001f7b : mov    %eax,%ecx
0x00001f7d : add    $0x4,%ecx
0x00001f80 : movl   $0x0,-0x4(%ebp)  
0x00001f87 : mov    %ecx,-0x8(%ebp)  
0x00001f8a : add    $0x8,%esp
0x00001f8d : pop    %ebp
0x00001f8e : ret   
可以看到,编译器和上面一样,也进行了优化,直接地址4放入变量p中。当然,这两个示例告诉我们,使用地址的形式很容易让编译器进行对应的可能优化。这也为程序员使用欺骗编译器来得到需要得到的数据埋下了伏笔。我们继续看如下代码,不取地址0处的地址,直接取地址0处的成员数据:
[cpp] view plaincopy
#include   
#include   
  
#define PRINT_D(intValue)     printf(#intValue" is %d\n",(intValue));  
#define OFFSET(struct, member)  ((char *)&((struct *)0)->member -(char *)0)  
  
typedef struct  
{  
   char    sex;  
   int     age;  
}student;
  
int main()
{  
   int age = ((student *)0)->age;
   return 0;  
}  
运行:
可以看出,出现了访问违例。因为如果代码非要访问地址0处的数据,编译器也不能再做什么优化,只能乖乖地去取数据,结果就违例了。
Q关于结构体的对齐,到底遵循什么原则?
A首先先不讨论结构体按多少字节对齐,先看看只以1字节对齐的情况:
[cpp] view plaincopy
#include   
#include   
  
#define PRINT_D(intValue)     printf(#intValue" is %d\n",(intValue));  
#define OFFSET(struct, member)  ((char *)&((struct *)0)->member -(char *)0)  
  
#pragma pack(1)  
typedef struct  
{  
   char    sex;  
   short   score;  
   int     age;  
}student;
  
int main()
{  
   PRINT_D(sizeof(student))  
   PRINT_D(OFFSET(student, sex))  
   PRINT_D(OFFSET(student, score))  
   PRINT_D(OFFSET(student, age))  
   return 0;  
}  
输出:
可以看到,如果按1字节对齐,那么结构体内部的成员紧密排列,sizeof(char) ==1, sizeof(short) == 2, sizeof(int) == 4.
修改上面的代码, 去掉#pragma pack语句,代码如下:
[cpp] view plaincopy
#include   
#include   
  
#define PRINT_D(intValue)     printf(#intValue" is %d\n",(intValue));  
#define OFFSET(struct, member)  ((char *)&((struct *)0)->member -(char *)0)  
  
typedef struct  
{  
   char    sex;  
   short   score;  
   int     age;  
}student;
  
int main()
{  
   PRINT_D(sizeof(student))  
   PRINT_D(OFFSET(student, sex))  
   PRINT_D(OFFSET(student, score))  
   PRINT_D(OFFSET(student, age))  
   return 0;  
}  
运行结果:
此时,各个成员之间就不像之前那样紧密排列了,而是有一些缝隙。这里需要介绍下对齐原则:
此原则是在没有#pragmapack语句作用时的原则(不同同台可能会有不同)
原则A 结构体或者union结构的成员,第一个成员在偏移0的位置,之后的每个成员的起始位置必须是当前成员大小的整数倍;
原则B 如果结构体A含有结构体成员B,那么B的起始位置必须是B中最大元素大小整数倍地址;
原则C 结构体的总大小,必须是内部最大成员的整数倍;
依据上面3个原则,我们来具体分析下:
sex在偏移0处,占1字节;scoreshort类型,占2字节,score必须以2的整数倍为起始位置,所以它的起始位置为2 ageint类型,大小为4字节,它必须以4的整数倍为起始位置,因为前面有sex1字节,填充的1字节和score2字节,地址4已经是4的整数倍,所以age的位置为4.最后,总大小为4的倍数,不用继续填充。
继续修改上面的代码,增加#pragmapack语句:
[cpp] view plaincopy
#include   
#include   
  
#define PRINT_D(intValue)     printf(#intValue" is %d\n",(intValue));  
#define OFFSET(struct, member)  ((char *)&((struct *)0)->member -(char *)0)  
  
#pragma pack(4)  
typedef struct  
{  
   char    sex;  
   short   score;  
   int        age;  
}student;
  
int main()
{  
   PRINT_D(sizeof(student))  
   PRINT_D(OFFSET(student, sex))  
   PRINT_D(OFFSET(student, score))  
   PRINT_D(OFFSET(student, age))  
   return 0;  
}  
运行结果:
具体分析下:
有了#pragmapack(4)语句后,之前说的原则AC就不适用了。实际对齐原则是自身对齐值(成员sizeof大小)和指定对齐值(#pragma pack指定的对齐大小)的较小者。依次原则,sex依然偏移为0自身对齐值为1,指定对齐值为4,所以实际对齐为1 score成员自身对齐值为2,指定对齐值为4,实际对齐为2;所以前面的sex后面将填充一个1字节,然后是score的位置,它的偏移为2age自身对齐值为4,指定对齐为4,所以实际对齐值为4;前面的sexscore正好占用4字节,所以age接着存放;它的偏移为4.
我们继续修改下代码:
[cpp] view plaincopy
#include   
#include   
  
#define PRINT_D(intValue)     printf(#intValue" is %d\n",(intValue));  
#define OFFSET(struct, member)  ((char *)&((struct *)0)->member -(char *)0)  
  
#pragma pack(4)  
typedef struct  
{  
   char    sex;  
   int     age;  
   short   score;  
}student;
  
int main()
{  
   PRINT_D(sizeof(student))  
   PRINT_D(OFFSET(student, sex))  
   PRINT_D(OFFSET(student, age))  
   PRINT_D(OFFSET(student, score))  
   return 0;  
}  
运行结果:
这个和上面的不同在于age成员被移到第二个位置;sex的偏移依然为0age自身对齐为4,指定对齐为4,所以实际对齐为4,所以age将从偏移为4的位置存储。
Q关于位域的问题,空域到底表示什么?
A它表示之后的位域从新空间开始。
[cpp] view plaincopy
#include   
#include   
  
#define PRINT_D(intValue)     printf(#intValue" is %d\n", (intValue));  
#define OFFSET(struct, member)  ((char *)&((struct *)0)->member -(char *)0)  
  
typedef struct   
{  
   int a : 1;  
   int b : 3;  
   int : 0;  
   int d : 2;  
}bit_info;
  
int main()
{  
   PRINT_D(sizeof(bit_info))  
   return 0;  
}  
运行结果:
bit_info中的a, b占用4个字节的前4位,到int : 0; 时表示此时将填充余下所有没有填充的位,即刚刚的4个字节的余下28位;int d : 2; 将从第四个字节开始填充,又会占用4个字节,所以总大小为8.
关于结构体如何和面向对象关联,c++很好地诠释了,这里不做介绍。
xichen
2012-5-18 11:06:47


[ 本帖最后由 tiankai001 于 2012-12-15 07:42 编辑 ]
此帖出自单片机论坛
 
 
 

回复

6366

帖子

4929

TA的资源

版主

12
 
异常处理,保证代码稳定的必经之步----小话c语言(12)
[Mac10.7.1 Lion  Intel-based  x64 gcc4.2.1]
Q c语言的异常处理可以使用什么?
A可以使用setjmplongjmp的组合。一个是保存处理异常前执行的环境,一个是调回原来执行的环境。
[cpp] view plaincopy
int setjmp(jmp_buf env);  
参数env的类型jmp_buf定义如下:
[cpp] view plaincopy
/*
*_JBLEN is number of ints required to save the following:
* eax,ebx, ecx, edx, edi, esi, ebp, esp, ss, eflags, eip,
*cs, de, es, fs, gs == 16 ints
*onstack, mask = 2 ints
*/  
  
#define _JBLEN (18)  
typedef int jmp_buf[_JBLEN];  
可以看到jmp_buf是个数组类型,含有18int类型数据,包括eax, ebp, esp, eip等环境变量。
它会返回0,便于外部判断进入异常处理逻辑。
[cpp] view plaincopy
void longjmp(jmp_buf env, int val);  
第一个参数为setjmp设置的jmp_buf, 第二个参数为返回异常处理的异常参数,可自定义。
下面是个简单的例子:
[cpp] view plaincopy
#include   
#include   
#include   
  
#define PRINT_D(intValue)     printf(#intValue" is %d\n",(intValue));  
#define PRINT_STR(str)      printf(#str" is %s\n",(str));  
  
jmp_buf buf;  
  
// exception process  
void exception_process()  
{  
   printf("exception process begin...\n");  
   longjmp(buf, 1);        // returnto the normal process  
   printf("never execute this...\n");  // so, this line can't be executed  
}  
  
int main()
{  
   int ret;  
   printf("main begin...\n");
   ret = setjmp(buf);  // savecurrent execute enviroment, go to exception process  
    if(ret)
   {  
       // returned by exception process  
       printf("pass exception process ...\n");  
       printf("main end ...\n");
   }  
   else  
   {  
       exception_process();  
   }  
     
   return 0;  
}  
可以看到setjmplongjmp因为共同操作了jmp_buf buf;全局变量,所以它们可以在不同函数跳转并正确返回执行。main函数开始setjmp(buf)一定会返回0, 所以进入exception_process例程,进入后,longjmp(buf, 1);会返回之前执行的地方,进入mainif(ret)逻辑中,执行异常发生后的代码。
执行结果:
Q上面代码中的buf全局变量可以采用局部变量吗?
A是的,但是需要将buf传递给异常处理部分。如下代码:
[cpp] view plaincopy
#include   
#include   
#include   
  
#define PRINT_D(intValue)     printf(#intValue" is %d\n",(intValue));  
#define PRINT_STR(str)      printf(#str" is %s\n",(str));  
  
  
// exception process  
void exception_process(jmp_buf buf)  
{  
   printf("exception process begin...\n");  
   longjmp(buf, 1);        // returnto the normal process  
   printf("never execute this...\n");  // so, this line can't be executed  
}  
  
int main()
{  
   jmp_buf buf;  
   int ret;  
    printf("main begin...\n");  
   ret = setjmp(buf);  // savecurrent execute enviroment, go to exception process  
   if(ret)  
   {  
       // returned by exception process  
       printf("pass exception process ...\n");  
       printf("main end ...\n");
   }  
   else  
   {  
       exception_process(buf);  
   }  
     
   return 0;  
}  
可以看到exception_process多了一个参数,用于buf的传递。
运行结果:
Q如果异常原因有几种,怎么分辨处理?
A这个就需要用到longjmp第二个参数了。
[cpp] view plaincopy
#include   
#include   
#include   
  
#define PRINT_D(intValue)     printf(#intValue" is %d\n",(intValue));  
#define PRINT_STR(str)      printf(#str" is %s\n",(str));  
  
  
// input error process  
void exception_input_error_process(jmp_bufbuf)  
{  
   printf("exception input error process begin...\n");  
   longjmp(buf, 1001);        //return to the normal process, 1001 means exception error number  
}  
  
// input too big process  
voidexception_input_too_big_process(jmp_buf buf)
{  
   printf("exception input too big process begin...\n");  
   longjmp(buf, 1002);        //return to the normal process, 1002 means exception error number  
}  
  
int main()
{  
   jmp_buf buf;  
   int ret, scanf_ret;  
   int n;  
   printf("main begin...\n");
     
   // input n  
   printf("input n:");  
   scanf_ret = scanf("%d", &n);  
     
    ret= setjmp(buf);  // save current executeenviroment, go to exception process  
   if(ret)  
   {  
       // returned by exception process  
       printf("pass exception process ...\n");  
       if(ret == 1001)  
       {  
           printf("exception 1001 process end...\n");  
       }  
       else if(ret == 1002)  
       {  
           printf("exception 1002 process end...\n");  
       }  
         
       printf("main end ...\n");
   }  
   else  
   {  
       if(scanf_ret < 1)  
           exception_input_error_process(buf);
       else if(n > 100)  
           exception_input_too_big_process(buf);
   }  
     
   return 0;  
}  
如上,如果输入整形格式不正确,那么进入输入错误处理;如果输入的整形超过100,那么进入输入过大的处理。
运行结果:
输入错误数据a:
输入数据为101:
输入数据50:
此时没有发生异常。
xichen
2012-5-18 15:18:16


[ 本帖最后由 tiankai001 于 2012-12-15 07:43 编辑 ]
此帖出自单片机论坛
 
 
 

回复

6366

帖子

4929

TA的资源

版主

13
 
多线程,它是让计算机更好用的东西,也是程序员最容易犯错的东西----小话c语言(13)
[Mac-10.7.1  Lion Intel-based  x64  gcc4.2.1]
Q  c标准中包含线程操作么?
A没有。
Q给个mac下线程操作的例子吧。
A创建线程的函数可以实用pthread_create, 原型如下:
[cpp] view plaincopy
int pthread_create(pthread_t *restrict thread,  
        const pthread_attr_t *restrict attr,  
        void *(*start_routine)(void *),  
        void *restrict arg);  
thread是保存成功创建线程结构信息; attr表示线程的相关属性,start_routine表示线程执行的函数, arg表示线程执行的使用的参数。
示例代码(保存为testForC.c)
[cpp] view plaincopy
#include   
#include   
#include   
#include   
  
#define PRINT_D(intValue)     printf(#intValue" is %d\n",(intValue));  
#define PRINT_STR(str)      printf(#str" is %s\n",(str));  
  
void   *thread_one_func(void *args)  
{  
   int i = 1;  
   while (i > 0)  
   {  
       printf("thread: loop %d\n", i++);  
   }  
   return NULL;  
}  
  
int main()
{  
   pthread_t thread;  
   int ret;  
   ret = pthread_create(&thread, NULL, thread_one_func, NULL);  
   if(ret != 0)  
   {  
       perror("pthread_create error");  
       return -1;  
   }  
     
   return 0;  
}  
运行:
可以看到,程序运行后然后很快就结束了,且没有任何输出。这是因为主线程过快结束导致结束了刚刚创建的子线程。
Q:如何让主线程不理解结束呢?
A可以让主线程进入等待状态,加入while(1)循环让主线程一直等待。
[cpp] view plaincopy
#include   
#include   
#include   
#include   
  
#define PRINT_D(intValue)       printf(#intValue" is %d\n",(intValue));  
#define PRINT_STR(str)          printf(#str" is %s\n",(str));  
#define FOR_EVER()              { while(1) ; }  
  
void   *thread_one_func(void *args)  
{  
   int i = 1;  
   while (i > 0)  
   {  
        printf("thread: loop %d\n",i++);  
   }  
   return NULL;  
}  
  
int main()
{  
   pthread_t thread;  
   int ret;  
   ret = pthread_create(&thread, NULL, thread_one_func, NULL);  
   if(ret != 0)  
   {  
       perror("pthread_create error");  
       return -1;  
   }  
     
   FOR_EVER();  
   return 0;  
}  
运行结果:
上面是在子线程输出字符串的过程中截取的,子线程会一直运行下去,知道自己的循环退出。
Q子线程运行的这么快,让它慢点行不?
A在子线程的while循环中加个延迟1秒的函数,如下:
[cpp] view plaincopy
#include   
#include   
#include   
#include   
  
#define PRINT_D(intValue)       printf(#intValue" is %d\n",(intValue));  
#define PRINT_STR(str)          printf(#str" is %s\n",(str));  
#define FOR_EVER()              { while(1) ; }  
  
void   *thread_one_func(void *args)  
{  
   int i = 1;  
   while (i > 0)  
   {  
       printf("thread: loop %d\n", i++);  
       sleep(1);       // sleep for 1second  
   }  
   return NULL;  
}  
  
int main()
{  
   pthread_t thread;  
   int ret;  
    ret= pthread_create(&thread, NULL, thread_one_func, NULL);  
   if(ret != 0)  
   {  
       perror("pthread_create error");  
       return -1;  
   }  
     
   FOR_EVER();  
   return 0;  
}  
运行结果:
从实际运行可以看出,子线程每打印一行会延迟一下。
Q主线程如何在规定的时间内干掉子线程?
A主线程可以启动定时器,在指定时间内干掉子线程。
[cpp] view plaincopy
int setitimer(int which,  const structitimerval *restrict value,  structitimerval *restrict ovalue);  
参数which表示定时器类型,value表示定时器时间信息,ovalue可以设置为NULL.
[cpp] view plaincopy
#include   
#include   
#include   
#include   
#include   
#include   
  
#define PRINT_D(intValue)       printf(#intValue" is %d\n",(intValue));  
#define PRINT_STR(str)          printf(#str" is %s\n",(str));  
#define FOR_EVER()              { while(1) ; }  
  
pthread_t  thread;  
int        should_loop = 1;  
  
void   kill_son_thread(int arg)  
{  
   printf("now it will kill son thread...\n");  
   pthread_cancel(thread);  
   should_loop = 0;  
}  
  
void   *thread_one_func(void *args)  
{  
   int i = 1;  
   while (i > 0)  
   {  
       printf("thread: loop %d\n", i++);  
       sleep(1);       // sleep for 1second  
   }  
   return NULL;  
}  
  
int main()
{  
   int ret;  
   struct itimerval timer;  
     
   ret = pthread_create(&thread, NULL, thread_one_func, NULL);  
   if(ret !=  0)  
   {  
       perror("pthread_create error");  
       return -1;  
   }  
     
   // set the timer  
   timer.it_value.tv_sec = 5;  
    timer.it_value.tv_usec = 0;  
   timer.it_interval.tv_sec = 5;  
   timer.it_interval.tv_usec = 0;  
     
   signal(SIGALRM, kill_son_thread);      // register a signal for timer action
   setitimer(ITIMER_REAL, &timer, NULL);   // start the timer  
   while(should_loop)  
       ;  
  
   return 0;  
}  
运行结果:
可以看到,主线程大约5秒后干掉了子线程,然后结束了。
Q pthread_exit不也可以退出线程么?
A是的。不用定时器,子线程5秒后自动退出,代码如下:
[cpp] view plaincopy
#include   
#include   
#include   
#include   
#include   
#include   
  
#define PRINT_D(intValue)       printf(#intValue" is %d\n",(intValue));  
#define PRINT_STR(str)          printf(#str" is %s\n",(str));  
#define FOR_EVER()              { while(1) ; }  
  
pthread_t  thread;  
int        should_loop = 1;  
  
void   *thread_one_func(void *args)  
{  
   int i = 1;  
   while (i > 0)  
   {  
       printf("thread: loop %d\n", i++);  
       sleep(1);       // sleep for 1second  
       if(i == 6)  
       {  
           should_loop = 0;  
           printf("[Son thread]: end...\n");  
           pthread_exit(NULL);  
       }  
   }  
   return NULL;  
}  
  
int main()
{  
   int ret;  
     
   ret = pthread_create(&thread, NULL, thread_one_func, NULL);  
   if(ret != 0)  
   {  
       perror("pthread_create error");  
       return -1;  
   }  
     
   while(should_loop)  
       ;  
   printf("[Main thread]: end...\n");  
   return 0;  
}  
运行结果:
Q还有个函数,pthread_join的作用是什么?
A它的作用是等待指定线程执行结束。它实现了线程之间的同步。
[cpp] view plaincopy
#include   
#include   
#include   
#include   
#include   
#include   
  
#define PRINT_D(intValue)       printf(#intValue" is %d\n",(intValue));  
#define PRINT_STR(str)          printf(#str" is %s\n",(str));  
#define FOR_EVER()              { while(1) ; }  
  
pthread_t  thread;  
int        should_loop = 1;  
  
void   *thread_one_func(void *args)  
{  
   int i = 1;  
   while (i > 0)  
   {  
       printf("thread: loop %d\n", i++);  
       sleep(1);       // sleep for 1second  
       if(i == 6)  
       {  
           should_loop = 0;  
           pthread_exit(NULL);  
       }  
   }  
   return NULL;  
}  
  
int main()
{  
   int ret;  
     
   ret = pthread_create(&thread, NULL, thread_one_func, NULL);  
   if(ret != 0)  
   {  
       perror("pthread_create error");  
       return -1;  
   }  
     
   pthread_join(thread, NULL);     //wait for the son thread's end  
   printf("[Son thread]: end...\n");  
   while(should_loop)  
       ;  
   printf("[Main thread]: end...\n");  
   return 0;  
}  
运行结果:
Q上面的情形,主线程需要等待子线程结束,如果主线程不必等待子线程结束,子线程为主线程提供必要的数据,二者同步运行,如何处理?
A那么,可以使用互斥体、信号量等。
[cpp] view plaincopy
#include   
#include   
#include   
#include   
#include   
#include   
  
#define PRINT_D(intValue)       printf(#intValue" is %d\n",(intValue));  
#define PRINT_STR(str)          printf(#str" is %s\n",(str));  
#define FOR_EVER()              { while(1) ; }  
  
pthread_t  thread;  
int        should_loop = 1;  
pthread_mutex_t mutex;  
int        data[3];  
  
void   *thread_one_func(void *args)  
{  
   int i = 1;  
   while (i > 0)  
   {  
       pthread_mutex_lock(&mutex);  
       data[0] = i;  
       data[1] = i + 1;  
       data[2] = i + 2;  
       ++i;  
       sleep(1);  
       pthread_mutex_unlock(&mutex);      
   }  
   return NULL;  
}  
  
int main()
{  
   int ret;  
   // create and init a mutex  
   pthread_mutex_init(&mutex, NULL);
     
   ret = pthread_create(&thread, NULL, thread_one_func, NULL);  
   if(ret != 0)  
   {  
       perror("pthread_create error");  
       return -1;  
   }  
     
   while(1)  
   {  
       pthread_mutex_lock(&mutex);  
       if(data[0] > 3)  
       {  
           pthread_mutex_unlock(&mutex);
           break;  
       }  
       printf("[Main thread]: %d %d %d\n", data[0], data[1],data[2]);  
       pthread_mutex_unlock(&mutex);
       sleep(1);         // avoid themain thread running too fast, thanks wufangna !  
    }  
   pthread_mutex_destroy(&mutex);
   printf("[Main thread]: end...\n");  
     
   return 0;  
}  
上面的代码,主线程从全局数据data中读取数据,子线程大约每1秒更新下data中的数据,当data[0]的值大于3的时候主线程退出。
运行结果:
Q还有种类型pthread_cond_t,它和pthread_mutex_t有什么区别?
A前者是在某种情况下设定的"互斥体", 后者可以当成无条件的互斥体;例子如下,
[cpp] view plaincopy
#include   
#include   
#include   
#include   
#include   
#include   
  
#define PRINT_D(intValue)       printf(#intValue" is %d\n",(intValue));  
#define PRINT_STR(str)          printf(#str" is %s\n",(str));  
#define FOR_EVER()              { while(1) ; }  
  
pthread_t  thread;  
int        should_loop = 1;  
pthread_mutex_t mutex;  
int        data[3];  
pthread_cond_t  cond;  
  
void   *thread_one_func(void *args)  
{  
   int i = 1;  
   while (i > 0)  
   {  
       pthread_mutex_lock(&mutex);  
       data[0] = i;  
       data[1] = i + 1;  
       data[2] = i + 2;  
       if(data[0] % 2 == 0)  
           pthread_cond_signal(&cond);  
       ++i;  
       sleep(1);  
       pthread_mutex_unlock(&mutex);      
   }  
   return NULL;  
}  
  
int main()
{  
   int ret;  
   // create and init a mutex  
   pthread_mutex_init(&mutex, NULL);
   pthread_cond_init(&cond, NULL);
     
   ret = pthread_create(&thread, NULL, thread_one_func, NULL);  
   if(ret  !=  0)  
    {  
       perror("pthread_create error");  
       return -1;  
   }  
     
   while(1)  
   {  
       pthread_mutex_lock(&mutex);  
       if(data[0] > 6)  
       {  
           pthread_mutex_unlock(&mutex);
           break;  
       }  
       pthread_cond_wait(&cond, &mutex);  
              
       printf("[Main thread]: %d %d %d\n", data[0], data[1],data[2]);  
       pthread_mutex_unlock(&mutex);
   }  
     
   pthread_cond_destroy(&cond);  
   pthread_mutex_destroy(&mutex);
   printf("[Main thread]: end...\n");  
     
   return 0;  
}  
上面的代码,主线程当发现data[0]大于6时就退出;小于6的时候,等待cond的状态变化;子线程会更新data数组中的数值,且当data[0]是偶数的时候就激活等待cond的线程(主线程),二者实现同步和互斥。
运行效果:
对于同步互斥,semaphore也可以实现; sem_open, sem_wait等等是操作它的函数,这里不再一一介绍。
xichen
2012-5-19 15:46:26
t5�''o�HXp:宋体;mso-ascii-font-family:"Times New Roman";mso-hansi-font-family:"Times New Roman"'>:关于位域的问题,空域到底表示什么?
A它表示之后的位域从新空间开始。
[cpp] view plaincopy
#include   
#include   
  
#define PRINT_D(intValue)     printf(#intValue" is %d\n", (intValue));  
#define OFFSET(struct, member)  ((char *)&((struct *)0)->member -(char *)0)  
  
typedef struct   
{  
   int a : 1;  
   int b : 3;  
   int : 0;  
   int d : 2;  
}bit_info;
  
int main()
{  
   PRINT_D(sizeof(bit_info))  
   return 0;  
}  
运行结果:
bit_info中的a, b占用4个字节的前4位,到int : 0; 时表示此时将填充余下所有没有填充的位,即刚刚的4个字节的余下28位;int d : 2; 将从第四个字节开始填充,又会占用4个字节,所以总大小为8.
关于结构体如何和面向对象关联,c++很好地诠释了,这里不做介绍。
xichen
2012-5-18 11:06:47


[ 本帖最后由 tiankai001 于 2012-12-15 07:56 编辑 ]
此帖出自单片机论坛
 
 
 

回复

6366

帖子

4929

TA的资源

版主

14
 
gcc编译命令----小话c语言(14)
[Mac 10.7.1 Lion  Intel-based  x64 gcc 4.2.1]
Q如何让编译的文件可以被gdb调试?
A可以加入-g参数。如下代码,保存为hello.c:
[cpp] view plaincopy
#include   
  
int main()
{  
   printf("hello world!\n");
   return 0;  
}  
编译 gcc  hello.c -o  hello得到hello.
使用  gdb hello进入调试,输入list:
可以看到提示没有符号被加载,这说明上面的编译命令没有加入调试信息,不能被gdb正常调试。
加入-g参数重新编译  gcc -g  hello.c  -o hello得到文件hello.
gdb hello进入调试,输入list:
可以看到,此时已经可以看到源代码了,接着就可以调试了。
Q如何编译程序能让gprof工具使用?
A使用-p参数。不过根据bug列表,intel架构的mac系统不能正常运行gprof, 以后将在ubuntu下将这个例子写出来。
Q如果源代码的扩展名不是gcc默认支持的扩展名,那么编译会出现什么情况?
A将上面的hello.c复制一个为hello.xichen,使用gcc -o hello  hello.xichen编译:
可以看到出现了问题;可以使用-x参数将文件当成指定类型的文件来编译:
gcc -x  c  hello.xichen -o  hello是将hello.xichen当成c代码编译:
可以看出已经正确编译了;-x参数后面可以跟随c, c++等类型名, 也可以跟随none来关闭指定为特定类型文件编译, 具体请参照gcc帮助文档。
Q如果需要测试代码是否符合ansi标准,怎么办?
A可以使用-ansi参数。如果是c代码,它对于不遵循c90标准的代码将出现编译问题。
对于//作为注释,从c99才开始支持,用此作为测试。
[cpp] view plaincopy
#include   
  
int main()
{  
   // c99 support // style comment  
   printf("hello world!\n");
   return 0;  
}  
保存为hello.c:
gcc -ansi  -o  hello hello.c编译:
使用gcc  -o hello  hello.c编译:
可以看到,这样就没有问题。
Q c语言有不同的标准,那么设定遵循哪种标准使用什么参数?
A可以使用-std=参数格式,可以跟随c99, gnu99等参数。
Q如何禁止将某些关键字当做关键字?
A可以使用-fno-asm参数来禁止asm, inline, typeof当做关键字。如下代码,保存为hello.c
[cpp] view plaincopy
#include   
  
inline int add(int a, int b)  
{  
   return a + b;  
}  
  
int main()
{  
   printf("hello world!\n");
   return 0;  
}  
使用gcc  -fno-asm hello.c  -o  hello编译:
可以看到出现了编译错误;使用gcc  hello.c -o  hello编译:
Q有时,有的编译器将char默认当做有符号的,有的当做无符号的,用编译器参数可以实现自由控制么?
A是的。可以使用-funsigned-char或者-fno-signed-char来将char自动当做unsigned类型;类似的,用-fsigned-char或者-fno-unsigned-char来将char自动当做signed类型。例子如下,代码保存为unsigned_signed_char.c:
[cpp] view plaincopy
#include   
  
int main()
{  
   char ch = 0xFF;  
   if(ch < 0)  
       printf("char is signed...\n");
   else  
       printf("char is unsigned...\n");  
   return 0;  
}  
使用gcc  unsigned_signed_char.c  -o unsigned_signed_char编译运行:
使用gcc  unsigned_signed_char.c  -o unsigned_signed_char -funsigned-char编译运行:
使用 gcc  unsigned_signed_char.c  -o unsigned_signed_char -fsigned-char来编译运行:
Q如果有的时候在一个文件中忘记包含了头文件,如何用编译参数插入头文件呢?
A可以使用-include参数。代码如下,保存为no_header.c:
[cpp] view plaincopy
int main()
{  
   fprintf(stdout, "no header file\n");  
   return 0;  
}  
使用gcc  no_header.c -o  no_header编译:
可以看到,编译出错了;改用gcc  no_header.c -o  no_header  -include /usr/include/stdio.h编译:
运行ok
同样,可以使用-DXXX或者-DXXX=YYY或者-UXXX或者-undef来定义宏获取取消定义宏(XXX表示宏名, YYY表示宏定义的字符串).
Q有时,有个宏在另外一个文件中,想把它的值作用到另一个文件中,如何做?
A可以使用-imacros 参数; -imacros后面跟着需要包含的宏名的文件。如下代码,保存为imacros.h:
[cpp] view plaincopy
#ifndef IMACROS_H  
#define IMACROS_H  
  
#define N  100  
  
double add(double, double);  
  
#endif
如下代码,保存为imacros.c(它和上面的imacros.h在同一个目录下):
[cpp] view plaincopy
#include   
  
int add(int, int);  
  
int main()
{  
   int i = N;  
   printf("i is %d\n", i);
   return 0;  
}  
可以看到imacros.c文件中的N没有被定义,imacros.h中有它的宏定义。使用gcc  imacros.c  -o imacros  -imacros  imacros.h编译:
可以看到编译ok,且imacros.h中的add函数声明和imacros.c中的add函数声明不一致的问题也没有任何警告,因为-imacros仅仅将imacros.h文件中的宏进行处理,其它没有处理。
运行结果:
修改imacros.c代码如下:
[cpp] view plaincopy
#include   
#include "imacros.h"  
  
int add(int, int);  
  
int main()
{  
   int i = N;  
   printf("i is %d\n", i);
   return 0;  
}  
采用gcc  imacros.c -o  imacros编译:
可以看到,编译出错了,就是因为add声明不一致导致的;这里就可以看出-imacros参数的作用了。
Q如果有的时候,为了方便管理,将一些头文件移动到另一个目录,代码中的头文件路径不想用绝对路径,命令行参数该如何设置呢?
A可以使用-I参数来实现添加额外头文件路径的方式。如下代码,保存为include_folder.c
[cpp] view plaincopy
#include   
  
int main()
{  
   fprintf(stdout, "hello\n");
   return 0;  
}  
hello.c的同目录下创建如下文件stdio.h:
[cpp] view plaincopy
#ifndef STDIO_H   
#define STDIO_H   
  
  
#endif
它基本没什么用途。
如果使用gcc  include_folder.c  -o include_folder编译没有任何问题。
如果使用gcc  include_folder.c  -o include_folder  -I.编译:
可以看出,编译出了问题,因为-I.编译参数将本目录加入了头文件搜索路径, 而且是优先在-I指定的路径下查找,include_folder.c包含了stdio.h头文件,且优先在本目录下查找,包含了一个没有任何用途的头文件,导致了后面的编译错误。和-I参数对于头文件路径修改类似的,还有-idirafter, -iprefix, -iwithprefix, -iwithprefixbefore参数。
Q如果一个makefile中在当时必须使用-I来包含指定的头文件路径,但是后来因为一些原因又不能继续使用,有命令可以解决这个问题么?
A是的。可以使用-I-来取消使用-I参数指定的额外的头文件路径,不过这个参数形式以后可能会废弃,建议采用-Iquote来代替它。如上面的代码,使用gcc  include_folder.c  -o include_folder  -I.  -I-或者gcc  include_folder.c  -o include_folder  -I.  -iquote都可以正确编译。
Q如果不想使用系统的头文件,想直接使用自己编写的头文件,这些头文件被存储在另一个目录下,怎么办?
A如下代码nostdinc.c
[cpp] view plaincopy
#include   
#include   
  
int main()
{  
   int i = NOSTDINC;  
   return 0;  
}  
nostdinc.c同目录下有个头文件stdio.h:
[cpp] view plaincopy
#ifndef STDIO_H   
#define STDIO_H   
  
#define NOSTDINC    99  
  
#endif
使用gcc  nostdinc.c -o  nostdinc  -I. -nostdinc编译:
可以看出,使用了-nostdinc参数,编译器将不在系统头文件路径下搜索, -I.将使得它在本目录下搜索,结果没找到stdlib.h头文件,报错了;可以去掉-nostdinc参数选项,使用gcc  nostdinc.c -o  nostdinc  -I.编译即可:
或者nostdinc.c源代码中将对stdlib.h头文件的包含代码去掉也可以。
Q有时需要对源代码进行预处理,但是其中的注释被删除了,对于分析预处理后代码可能有影响,如何让预处理过程不删除注释?
A可以使用-C参数来实现,它一般都和-E参数搭配使用。如下代码simple_hello.c:
[cpp] view plaincopy
int main()
{  
   // just write something  
   return 0;  
}  
使用gcc  -E simple_hello.c预处理,得到内容:
[cpp] view plaincopy
# 1 "simple_hello.c"  
# 1 ""  
# 1 ""  
# 1 "simple_hello.c"  
  
int main()
{  
  
return 0;
}  
可以看到,注释被删除了。
如果采用gcc  -C -E  simple_hello.c预处理,得到如下内容:
[cpp] view plaincopy
# 1 "simple_hello.c"  
# 1 ""  
# 1 ""  
# 1 "simple_hello.c"  
  
int main()
{  
//just write something  
return 0;
}  
可以发现注释依然存在。
Q有时我需要查看一个源代码的依赖关系,如何查看?
A可以使用-M参数来获得。例如,如下代码,保存为hello.c:
[cpp] view plaincopy
#include   
  
int main()
{  
   printf("hello, xichen\n");
   return 0;  
}  
使用gcc  -M hello.c得到内容:
[cpp] view plaincopy
hello.o: hello.c /usr/include/stdio.h/usr/include/sys/cdefs.h \  
/usr/include/sys/_symbol_aliasing.h \
/usr/include/sys/_posix_availability.h /usr/include/Availability.h\  
/usr/include/AvailabilityInternal.h /usr/include/_types.h \  
/usr/include/sys/_types.h /usr/include/machine/_types.h \  
/usr/include/i386/_types.h /usr/include/secure/_stdio.h \  
/usr/include/secure/_common.h  
可以看出,hello.c的依赖关系。
类似的还有-MD, -MM,-MMD参数。
Q如果希望将一个编译选项传递给gcc内部调用的汇编程序或者链接程序,如何传递?
A可以使用-Wa,option的方式将option传递给汇编程序;可以使用-Wl,option的方式将option传递给链接程序。
Q有时,我需要生成stabs格式的调试信息,如何生成?
A可以使用-gstabs命令得到,但是它不会生成gdb调试信息,所以无法再用gdb对生成的可执行文件进行调试。类似的编译参数还有-gstabs+,-ggdb.
Q如果希望使用静态链接或者动态链接,或者生成一个共享库,使用什么参数?
A可以使用-static或者-shared参数。但是,在mac系统上,-static基本没什么用,-shared参数它不支持。
Q如何希望体验古老的c语言的特性,可以使用什么参数?
A可以使用-traditional参数。具体的例子笔者也写不出来,写了几个看起来很古老的语法,结果gcc都正常给编译通过了。
Q如果需要生成针对某个平台的优化代码,如何办?
A可以使用-O相关的参数进行优化,也可以使用-mtune=cpu_type进行具体平台优化。
Q如果需要不显示警告信息或者显示所有能显示的警告信息,如何办?
A可以使用-w参数或者-Wall参数。如下代码,保存为warning.c:
[cpp] view plaincopy
#include   
  
void main()
{  
   int i;  
   printf("hello, xichen\n");
}  
使用gcc  warning.c -o  warning编译:
使用gcc  warning.c -o  warning  -Wall编译:
可以看出,包括i没有被使用的警告也被显示出来;
使用gcc  warning.c -o  warning  -w编译:
可以看到没有任何警告信息显示了。
xichen
2012-5-19 22:26:32


[ 本帖最后由 tiankai001 于 2012-12-15 07:57 编辑 ]
此帖出自单片机论坛
 
 
 

回复

6366

帖子

4929

TA的资源

版主

15
 
gdb调试命令----小话c语言(15)
[Mac-10.7.1 Lion  Intel-based  x64 gcc4.2.1  GNU gdb 6.3.50-20050815(Apple version gdb-1708)]
Q给个简单的代码,然后进入调试状态。
A如下代码,保存为hello.c:
[cpp] view plaincopy
#include   
  
int main()
{  
   int i = 2;  
   printf("%d\n", i);  
   return 0;  
}  
gcc编译,需要有-g参数:gcc  -g hello.c  -o  hello得到带调试信息的hello可执行文件,gdb  hello进入调试状态,输入list查看源代码信息:
可以看到hello.c源代码被打印出来了。输入run命令运行程序,
可以看到程序开始运行,且输出了结果。
Q如何能够看到调试信息?
A可以通过源代码编译后的汇编代码得到信息。gcc  -S -g  hello.c得到hello.s文件:
[cpp] view plaincopy
   .section   __TEXT,__text,regular,pure_instructions
   .section   __DWARF,__debug_frame,regular,debug
Lsection_debug_frame:  
   .section   __DWARF,__debug_info,regular,debug
Lsection_info:  
   .section   __DWARF,__debug_abbrev,regular,debug
Lsection_abbrev:  
   .section   __DWARF,__debug_aranges,regular,debug
Lsection_aranges:  
   .section   __DWARF,__debug_macinfo,regular,debug
Lsection_macinfo:  
Lsection_line:  
   .section   __DWARF,__debug_loc,regular,debug
Lsection_loc:  
   .section   __DWARF,__debug_pubnames,regular,debug
Lsection_pubnames:  
   .section    __DWARF,__debug_pubtypes,regular,debug  
Lsection_pubtypes:  
   .section   __DWARF,__debug_str,regular,debug
Lsection_str:  
   .section   __DWARF,__debug_ranges,regular,debug
Lsection_ranges:  
   .section   __TEXT,__text,regular,pure_instructions
Ltext_begin:  
   .section    __DATA,__data  
Ldata_begin:  
   .section   __TEXT,__text,regular,pure_instructions
   .globl  _main  
   .align  4, 0x90  
_main:
Leh_func_begin1:  
Lfunc_begin1:  
Ltmp3:
   pushq   %rbp  
Ltmp0:
   movq    %rsp, %rbp  
Ltmp1:
Ltmp4:
   subq    $16, %rsp  
Ltmp2:
   movl    $2, -12(%rbp)  
Ltmp5:
   movl    -12(%rbp), %eax  
   xorb    %cl, %cl  
   leaq    L_.str(%rip), %rdx  
   movq    %rdx, %rdi  
   movl    %eax, %esi  
   movb    %cl, %al  
   callq   _printf  
Ltmp6:
   movl    $0, -8(%rbp)  
   movl    -8(%rbp), %eax  
   movl    %eax, -4(%rbp)  
   movl    -4(%rbp), %eax  
   addq    $16, %rsp  
   popq    %rbp  
   ret  
Ltmp7:
Lfunc_end1:
Leh_func_end1:  
  
    .section    __TEXT,__cstring,cstring_literals  
L_.str:
   .asciz   "%d\n"  
  
   .section   __TEXT,__eh_frame,coalesced,no_toc+strip_static_syms+live_support  
EH_frame0:
Lsection_eh_frame:  
Leh_frame_common:  
Lset0 = Leh_frame_common_end-Leh_frame_common_begin  
   .long   Lset0  
Leh_frame_common_begin:  
   .long   0  
   .byte   1  
   .asciz   "zR"  
   .byte   1  
   .byte   120  
   .byte   16  
   .byte   1  
   .byte   16  
   .byte   12  
   .byte   7  
   .byte   8  
   .byte   144  
   .byte   1  
   .align  3  
Leh_frame_common_end:  
   .globl  _main.eh  
_main.eh:
Lset1 =Leh_frame_end1-Leh_frame_begin1  
   .long   Lset1  
Leh_frame_begin1:  
Lset2 =Leh_frame_begin1-Leh_frame_common  
   .long   Lset2  
Ltmp8:
   .quad   Leh_func_begin1-Ltmp8  
Lset3 = Leh_func_end1-Leh_func_begin1  
   .quad   Lset3  
   .byte   0  
   .byte   4  
Lset4 = Ltmp0-Leh_func_begin1  
   .long   Lset4  
   .byte   14  
   .byte   16  
   .byte   134  
   .byte   2  
   .byte   4  
Lset5 = Ltmp1-Ltmp0  
   .long   Lset5  
   .byte   13  
   .byte   6  
   .align  3  
Leh_frame_end1:  
  
   .section   __TEXT,__text,regular,pure_instructions
Ltext_end:
   .section    __DATA,__data  
Ldata_end:
   .section    __TEXT,__text,regular,pure_instructions  
Lsection_end1:  
   .section   __DWARF,__debug_frame,regular,debug
Ldebug_frame_common:  
Lset6 =Ldebug_frame_common_end-Ldebug_frame_common_begin  
   .long   Lset6  
Ldebug_frame_common_begin:  
   .long   4294967295  
   .byte   1  
   .byte   0  
   .byte   1  
   .byte   120  
   .byte   16  
   .byte   12  
   .byte   7  
   .byte   8  
   .byte   144  
   .byte   1  
   .align  2  
Ldebug_frame_common_end:  
Lset7 = Ldebug_frame_end1-Ldebug_frame_begin1  
   .long   Lset7  
Ldebug_frame_begin1:  
Lset8 =Ldebug_frame_common-Lsection_debug_frame
   .long   Lset8  
   .quad   Lfunc_begin1  
Lset9 = Lfunc_end1-Lfunc_begin1  
   .quad   Lset9  
   .byte   4  
Lset10 = Ltmp0-Lfunc_begin1  
    .long  Lset10  
   .byte   14  
   .byte   16  
   .byte   134  
   .byte   2  
   .byte   4  
Lset11 = Ltmp1-Ltmp0  
   .long   Lset11  
   .byte   13  
   .byte   6  
   .align  2  
Ldebug_frame_end1:  
   .section   __DWARF,__debug_info,regular,debug
Linfo_begin1:  
   .long   201  
   .short  2  
Lset12 = Labbrev_begin-Lsection_abbrev  
   .long   Lset12  
   .byte   8  
   .byte   1  
   .ascii   "4.2.1 (Based onApple Inc. build 5658) (LLVM build 2336.1.00)"  
   .byte   0  
    .byte   1  
   .ascii   "hello.c"  
   .byte   0  
   .quad   0  
   .long   0  
   .ascii  "/Users/xichen/codes/c_simple/"  
   .byte   0  
   .byte   2  
   .byte   5  
   .ascii   "int"  
   .byte   0  
   .byte   4  
   .byte   3  
   .ascii   "main"  
   .byte   0  
   .ascii   "main"  
   .byte   0  
   .byte   1  
   .byte   4  
   .byte   1  
   .long   125  
   .byte   1  
   .quad   Lfunc_begin1  
   .quad   Lfunc_end1  
   .byte   1  
   .byte   86  
   .byte   4  
    .quad   Ltmp4
   .quad   Ltmp7  
   .byte   5  
   .byte   105  
   .byte   0  
   .byte   1  
   .byte   5  
   .long   125  
   .byte   2  
   .byte   145  
   .byte   116  
   .byte   0  
   .byte   0  
   .byte   0  
   .byte   0  
    .byte   0  
   .byte   0  
   .byte   0  
Linfo_end1:
   .section   __DWARF,__debug_abbrev,regular,debug
Labbrev_begin:  
   .byte   1  
   .byte   17  
   .byte   1  
   .byte   37  
   .byte   8  
   .byte   19  
   .byte   11  
   .byte   3  
   .byte   8  
   .byte   82  
   .byte   1  
   .byte   16  
   .byte   6  
   .byte   27  
   .byte   8  
   .byte   0  
   .byte   0  
   .byte   2  
   .byte   36  
   .byte   0  
   .byte   62  
   .byte   11  
   .byte   3  
    .byte   8  
   .byte   11  
   .byte   11  
   .byte   0  
   .byte   0  
   .byte   3  
   .byte   46  
   .byte   1  
   .byte   3  
   .byte   8  
   .byte   135  
   .byte   64  
   .byte   8  
   .byte   58  
   .byte   11  
   .byte   59  
   .byte   11  
   .byte   39  
   .byte   12  
   .byte   73  
   .byte   19  
   .byte   63  
   .byte   12  
   .byte   17  
   .byte   1  
   .byte   18  
   .byte   1  
   .byte   64  
   .byte   10  
   .byte   0  
   .byte   0  
   .byte   4  
   .byte   11  
   .byte   1  
   .byte   17  
   .byte   1  
   .byte   18  
   .byte   1  
   .byte   0  
   .byte   0  
   .byte   5  
   .byte   52  
   .byte   0  
   .byte   3  
   .byte   8  
   .byte   58  
   .byte   11  
   .byte   59  
   .byte   11  
   .byte   73  
   .byte   19  
   .byte   2  
   .byte   10  
   .byte   0  
   .byte   0  
   .byte   0  
Labbrev_end:  
   .section   __DWARF,__debug_line,regular,debug
Lset13 = Lline_end-Lline_begin  
   .long   Lset13  
Lline_begin:  
   .short  2  
Lset14 =Lline_prolog_end-Lline_prolog_begin  
   .long   Lset14  
Lline_prolog_begin:  
   .byte   1  
   .byte   1  
   .byte   246  
   .byte   245  
   .byte   10  
   .byte   0  
   .byte   1  
   .byte   1  
   .byte   1  
   .byte   1  
   .byte   0  
   .byte   0  
   .byte   0  
   .byte   1  
   .asciz  "/Users/xichen/codes/c_simple/"  
   .byte   0  
   .asciz   "hello.c"  
   .byte   1  
   .byte   0  
   .byte   0  
    .byte   0  
Lline_prolog_end:  
   .byte   0  
   .byte   9  
   .byte   2  
   .quad   Ltmp3  
   .byte   23  
   .byte   0  
   .byte   9  
   .byte   2  
   .quad   Ltmp4  
   .byte   21  
   .byte   0  
   .byte   9  
   .byte   2  
   .quad   Ltmp5  
   .byte   21  
   .byte   0  
   .byte   9  
   .byte   2  
   .quad   Ltmp6  
   .byte   21  
   .byte   0  
   .byte   9  
   .byte   2  
   .quad   Lsection_end1  
   .byte   0  
   .byte   1  
   .byte   1  
Lline_end:
    .section    __DWARF,__debug_pubnames,regular,debug  
Lset15 =Lpubnames_end1-Lpubnames_begin1  
   .long   Lset15  
Lpubnames_begin1:  
   .short  2  
Lset16 = Linfo_begin1-Lsection_info  
   .long   Lset16  
Lset17 = Linfo_end1-Linfo_begin1  
   .long   Lset17  
   .long   132  
   .asciz   "main"  
   .long   0  
Lpubnames_end1:  
   .section   __DWARF,__debug_pubtypes,regular,debug
Lset18 =Lpubtypes_end1-Lpubtypes_begin1  
   .long   Lset18  
Lpubtypes_begin1:  
   .short  2  
Lset19 = Linfo_begin1-Lsection_info  
   .long   Lset19  
Lset20 = Linfo_end1-Linfo_begin1  
   .long   Lset20  
   .long   0  
Lpubtypes_end1:  
   .section   __DWARF,__debug_aranges,regular,debug
   .section   __DWARF,__debug_ranges,regular,debug
   .section   __DWARF,__debug_macinfo,regular,debug
   .section   __DWARF,__debug_inlined,regular,debug
Lset21 =Ldebug_inlined_end1-Ldebug_inlined_begin1
   .long   Lset21  
Ldebug_inlined_begin1:  
   .short  2  
   .byte   8  
Ldebug_inlined_end1:  
  
.subsections_via_symbols  
可以看到里面有很多关于debug的信息,使用MachOView工具也可以看到对应的可执行文件中的调试信息。
Q如何调试core文件?
A可以使用gdb  可执行文件  core文件 的方式来调试。如下,写个可以导致崩溃的代码invalid_ptr.c:
[cpp] view plaincopy
#include   
  
int main()
{  
   int *p = (int *)0;  
   *p = 1;  
   return 0;  
}  
使用-g参数编译成可执行文件invalid_ptr.先查看下系统是否支持生成core文件,
如果是0,那说明不能生成core文件。做如下修改:
接着运行invalid_ptr文件:
注意上面的这个过程可能很慢,因为mac下生成的core文件异常的大,就这个代码,在笔者的机器上core文件有340MB,有点费解。
然后使用gdb调试此core文件(命令行中的/cores/core.13890是刚刚生成的core文件,不同平台可能不同,需要根据时间自行判断)
可以看到,它正确地定位到了错误代码的位置。
Q如何能够得到一个变量的类型?
A使用whatis命令即可。如下代码保存为mem.c:
[cpp] view plaincopy
#include   
  
int main()
{  
   int arr[] = {1, 2, 3, 4};  
   arr[0] = 100;  
   return 0;  
}  
使用gdb调试:
Qgdb环境,需要调用外部shell命令,如何不退出gdb来执行?
A使用shell命令即可。
上面是在gdb环境输出hello.c文件的内容。
Q有的时候,调试外部代码,为了给外部代码返回正确的数值,强制让一个函数返回某个特定的值,如何做?
A使用return命令。如下代码,保存为add.c, 并进入gdb调试状态:
[cpp] view plaincopy
#include   
  
int add(int a, int b)  
{  
   return a + b;  
}  
  
int main()
{  
   int i = 2;  
   int sum;  
   printf("%d\n", i);  
   sum = add(i, 3);  
   printf("sum is %d\n", sum);
  
   return 0;  
}  
如下调试:
可以看到,add函数,参数为23,但是可以强制改变它的返回值。
Q有的时候,需要修改某个变量的值来调试查看可能的结果,怎么做?
A可以使用set  var命令。使用上面的代码,进入调试状态:
可以看到使用set  var i=100i的数值改变了。
Q如果需要查看当前执行的函数的内部基本信息,怎么办?
A可以使用info  frame命令(缩写为i  f).依然使用上面的add.c代码,进入调试状态:
可以看到,里面包含了语言、参数、局部变量和一些寄存器信息等。
Q如果需要输出某个地址的数据,用什么命令?
A可以使用print  *地址命令。依然使用add.c代码,进入调试状态:
对于内存地址的查看,也可以使用x命令查看。
Q有的时候,想直接跳到某一行执行,怎么做?
A使用jump命令。如下代码,为loop.c:
[cpp] view plaincopy
#include   
  
int main()
{  
   int i = 2;  
   while(i < 1000)  
   {  
       ++i;  
   }  
   printf("end...\n");  
   return 0;  
}  
假设在while循环中有断点,想要直接跳到后面的printf函数:
Q如果在调试状态,需要主动调用某个函数,求其返回值,怎么做?
A可以使用call命令。使用add.c代码:
[cpp] view plaincopy
#include   
  
int add(int a, int b)  
{  
   return a + b;  
}  
  
int main()
{  
   int i = 2;  
   int sum;  
   printf("%d\n", i);  
   sum = add(i, 3);  
   printf("sum is %d\n", sum);
  
   return 0;  
}  
进入调试状态:
Q如果是多线程,如何给不同线程执行的代码打断点?
A可以使用info  threads获取所有线程信息,然后使用b  行号 thread  线程号 来打断点。
xichen
2012-5-22 11:11:34


[ 本帖最后由 tiankai001 于 2012-12-15 07:57 编辑 ]
此帖出自单片机论坛
 
 
 

回复

6366

帖子

4929

TA的资源

版主

16
 
开发实用命令和工具----小话c语言(16)
[Mac10.7.1  Lion  Intel-based  x64  gcc4.2.1]
Q: 有的时候,记得在某个目录下写过某个变量或者其它什么文本形式的东西,但是后来忘记写在哪个文件里了,怎么找到?
A: 这个就需要用到grep命令了。它很强大,尤其对于开发或者寻找某个东西在哪里的时候。举个例子,有个目录里面有一些文件:
file:///C:/DOCUME~1/ADMINI~1/LOCALS~1/Temp/msohtml1/01/clip_image002.gif
而且,有一些文件里面含有main字符串,现在需要把它们找出来:
file:///C:/DOCUME~1/ADMINI~1/LOCALS~1/Temp/msohtml1/01/clip_image004.gif
很可惜,在mac10.7.1下面,grep的一个参数-r似乎无效,不能递归到子目录下搜索;不过在cygwin里面是ok的;bug列表中没有找到,但是实践确实无效,也许是个bug吧。
上面测试用的grep版本是2.5.1.
当然,它还支持很多参数,比如-i忽略大小写,-E进行egrep扩展等等。
Q: 经常会遇到,创建一个进程,但是后来想把它关闭,ps-ax命令得到一堆进程,不便于寻找,怎么快速能定位它?
A: 同样可以使用上面的grep命令。例如,需要寻找系统是否启动了httpd进程:
file:///C:/DOCUME~1/ADMINI~1/LOCALS~1/Temp/msohtml1/01/clip_image006.gif
Q: 有时,需要分析一个生成的可执行文件的内部结构,怎么办?
A: 使用otool命令,它异常的强大。它支持很多参数,帮您将可执行文件,也包括中间文件、库等文件的内部展示地一览无余。
Q: 我需要查看一个可执行文件的mach头,怎么办?
A: 可以使用-h参数。如下,写一个简单的代码,保存为hello.c:
1.     #include   
2.      
3.     int main()  
4.     {  
5.         int i = 2;  
6.         printf("%d\n", i);  
7.         return 0;  
8.     }  


使用gcc -o  hello  hello.c编译得到hello.它将对hello进行分析:
file:///C:/DOCUME~1/ADMINI~1/LOCALS~1/Temp/msohtml1/01/clip_image008.gif
用它和系统的machheader头文件结构对应,就可以得到它的具体含义。同理,使用-l命令可以得到mach-o格式文件的loadcommands.
Q: 我想得到某个可执行文件依赖哪些动态库,怎么办?
A: 可以使用otool命令的-L参数即可。同上使用上面的hello文件,
file:///C:/DOCUME~1/ADMINI~1/LOCALS~1/Temp/msohtml1/01/clip_image010.gif
Q: 我想知道一个可执行文件它的代码段,怎么办?
A: 可以使用otool命令的-t参数。同样使用上面的可执行文件hello,
file:///C:/DOCUME~1/ADMINI~1/LOCALS~1/Temp/msohtml1/01/clip_image012.gif
不过,它是二进制形式的。
Q: 怎么才能显示上面的二进制形式的汇编形式代码?
A: 可以配合-V命令或者-v命令(二者有一定区别)。
1.     xichenMac:c_simple xichen$ otool -Vt hello  
2.     hello:  
3.     (__TEXT,__text) section  
4.     start:  
5.     0000000100000eb0    pushq   $0x00  
6.     0000000100000eb2    movq    %rsp,%rbp  
7.     0000000100000eb5    andq    $0xf0,%rsp  
8.     0000000100000eb9    movq    0x08(%rbp),%rdi  
9.     0000000100000ebd    leaq    0x10(%rbp),%rsi  
10.  0000000100000ec1    movl    %edi,%edx  
11.  0000000100000ec3    addl    $0x01,%edx  
12.  0000000100000ec6    shll    $0x03,%edx  
13.  0000000100000ec9    addq    %rsi,%rdx  
14.  0000000100000ecc    movq    %rdx,%rcx  
15.  0000000100000ecf    jmp 0x100000ed5  
16.  0000000100000ed1    addq    $0x08,%rcx  
17.  0000000100000ed5    cmpq    $0x00,(%rcx)  
18.  0000000100000ed9    jne 0x100000ed1  
19.  0000000100000edb    addq    $0x08,%rcx  
20.  0000000100000edf    callq   _main  
21.  0000000100000ee4    movl    %eax,%edi  
22.  0000000100000ee6    callq   0x100000f2e ; symbol stub for: _exit  
23.  0000000100000eeb    hlt  
24.  0000000100000eec    nop  
25.  0000000100000eed    nop  
26.  0000000100000eee    nop  
27.  0000000100000eef    nop  
28.  _main:  
29.  0000000100000ef0    pushq   %rbp  
30.  0000000100000ef1    movq    %rsp,%rbp  
31.  0000000100000ef4    subq    $0x10,%rsp  
32.  0000000100000ef8    movl    $0x00000002,0xf4(%rbp)  
33.  0000000100000eff    movl    0xf4(%rbp),%eax  
34.  0000000100000f02    xorb    %cl,%cl  
35.  0000000100000f04    leaq    0x00000055(%rip),%rdx  
36.  0000000100000f0b    movq    %rdx,%rdi  
37.  0000000100000f0e    movl    %eax,%esi  
38.  0000000100000f10    movb    %cl,%al  
39.  0000000100000f12    callq   0x100000f34 ; symbol stub for: _printf  
40.  0000000100000f17    movl    $0x00000000,0xf8(%rbp)  
41.  0000000100000f1e    movl    0xf8(%rbp),%eax  
42.  0000000100000f21    movl    %eax,0xfc(%rbp)  
43.  0000000100000f24    movl    0xfc(%rbp),%eax  
44.  0000000100000f27    addq    $0x10,%rsp  
45.  0000000100000f2b    popq    %rbp  
46.  0000000100000f2c    ret  
47.  xichenMac:c_simple xichen$   

可以看到,对应的汇编已经出来了。
Q: 我想查看__TEXT段的字符串字面量,怎么查看?
A: 可以使用-sv __TEXT  __cstring参数来得到。修改hello.c, 如下:
1.     #include   
2.      
3.     int g_i = 0xAA;  
4.     const int g_j = 0xBB;  
5.     char *str = "hello";  
6.      
7.     int main()  
8.     {  
9.         int i = 2;  
10.      printf("%d\n", i);  
11.      return 0;  
12.  }  

编译成hello.
file:///C:/DOCUME~1/ADMINI~1/LOCALS~1/Temp/msohtml1/01/clip_image014.gif
可以看到,全局的str对应的hello\0和printf输出的%d\n\0字符串被显示了
Q: 我想查看数据区__DATA里面的数据(__data),怎么办?
A: 可以使用-sv __DATA  __data参数来得到。使用上面的hello,
file:///C:/DOCUME~1/ADMINI~1/LOCALS~1/Temp/msohtml1/01/clip_image016.gif
可以看到前4个字节对应的全局变量g_i在此被显示。如果使用otool -d  hello会查看hello的__DATA区的数据。
Q: 我想查看全局const变量g_i对应的数据,怎么查看?
A: 可以使用-sv __TEXT  __const参数来查看。
file:///C:/DOCUME~1/ADMINI~1/LOCALS~1/Temp/msohtml1/01/clip_image018.gif
可以看到0xBB变量的数据在此展示了。
Q: 我只想查看某个函数的汇编形式,怎么办?
A: 可以使用-p参数来指定查看的函数。如下:
file:///C:/DOCUME~1/ADMINI~1/LOCALS~1/Temp/msohtml1/01/clip_image020.gif
上面查看了hello中的main函数的汇编形式(注意,虽然代码中是main函数,但是在可执行文件的符号表中,可能会被改变,这依赖编译器).
Q: 有的可执行文件支持不止一种硬件架构的代码,如何查看包含哪些?
A: 可以使用file命令查看。如上,查看上面的hello文件的信息:
file:///C:/DOCUME~1/ADMINI~1/LOCALS~1/Temp/msohtml1/01/clip_image022.gif
可以看到它是64位运行于x64平台上的可执行文件。
Q: 如何编译得到一个可执行文件,它是通用的,可以包含几种架构代码的?
A: 使用gcc的-arch参数功能来实现。依然使用上面的hello.c代码:
file:///C:/DOCUME~1/ADMINI~1/LOCALS~1/Temp/msohtml1/01/clip_image024.gif
再运行file命令:
file:///C:/DOCUME~1/ADMINI~1/LOCALS~1/Temp/msohtml1/01/clip_image026.gif
可以看到,文件包含了两种架构体系代码。
Q: 看到很多逆向工具,可以修改特定的目标文件实现特定的目的,是否可以修改hello文件来得到希望的执行效果?
A: 是的。我并不善于修改汇编代码,我们可以修改数据段来查看效果。
修改上面的hello.c代码如下:
1.     #include   
2.      
3.     int g_i = 0xAA;  
4.     const int g_j = 0xBB;  
5.     char *str = "hello";  
6.      
7.     int main()  
8.     {  
9.         int i = 2;  
10.      printf("%s %d\n", str, i);  
11.      return 0;  
12.  }  


编译成hello.并查看执行效果:
file:///C:/DOCUME~1/ADMINI~1/LOCALS~1/Temp/msohtml1/01/clip_image028.gif
下面就使用vi来修改可执行文件的hello字符串为iello,最后查看执行效果。使用vi打开hello文件,定位到hello字符串的位置:
file:///C:/DOCUME~1/ADMINI~1/LOCALS~1/Temp/msohtml1/01/clip_image030.gif
修改hello的h为i:
file:///C:/DOCUME~1/ADMINI~1/LOCALS~1/Temp/msohtml1/01/clip_image032.gif
保存,然后执行hello:
file:///C:/DOCUME~1/ADMINI~1/LOCALS~1/Temp/msohtml1/01/clip_image034.gif
可以看到,执行结果已经变了。
Q: 有时,需要查看一个目标文件中各个段的大小,如何查看?
A: 可以使用size命令。
file:///C:/DOCUME~1/ADMINI~1/LOCALS~1/Temp/msohtml1/01/clip_image036.gif
Q: 有时,需要查看目标文件中可打印的字符串有哪些,如何查看?
A: 可以使用strings命令。
file:///C:/DOCUME~1/ADMINI~1/LOCALS~1/Temp/msohtml1/01/clip_image038.gif
Q: 很多编译好的程序,里面包含了符号表或者一些调试信息,如何去除它们?
A: 可以使用strip命令。
file:///C:/DOCUME~1/ADMINI~1/LOCALS~1/Temp/msohtml1/01/clip_image040.gif
使用strip命令后:
file:///C:/DOCUME~1/ADMINI~1/LOCALS~1/Temp/msohtml1/01/clip_image042.gif
可以看到,符号信息确实少了。
Q: 上面提到的nm命令,它是做什么的?
A: 它是可以查看目标文件中包含的符号信息的。
xichen
2012-5-2512:50:52


[ 本帖最后由 tiankai001 于 2012-12-15 07:58 编辑 ]
此帖出自单片机论坛
 
 
 

回复

6366

帖子

4929

TA的资源

版主

17
 
c()----小话c语言(17)
[Win7  vs2010]
Q C库和系统api之间是什么关系?
A如下图,简单示意:
可以看出,C库一部分是使用系统api实现自身功能(比如文件操作),另一部分并不会直接依赖系统api,单独实现功能(比如字符串处理)。另外,对于驱动模块,按照不同的理解,也可以放入操作系统内部或者操作系统下层;如果把操作系统看成隐形的CPU和内存的驱动,那么它也可以看成和常规意义的硬件驱动是平级的。而,C库,从理论上来说,没必要和驱动有依赖关系。当然,访问操作系统的方式不仅仅是它提供的api,也是可以通过其它方式来访问。
Qc库和多线程到底什么关系?
A多线程在操作系统上的运用导致了很多库,包括之前的单线程版本的c库,也必须做出相应修改,才能保证运行不会出现问题。例如,如下是vs2010附带的fgets.c中部分源代码:
[cpp] view plaincopy
_TSCHAR * __cdecl _fgetts (  
       _TSCHAR *string,  
       int count,  
       FILE *str  
       )  
{  
   REG1 FILE *stream;  
   REG2 _TSCHAR *pointer = string;  
   _TSCHAR *retval = string;  
   int ch;  
  
   _VALIDATE_RETURN(( string != NULL ) || ( count == 0 ), EINVAL,NULL);  
   _VALIDATE_RETURN(( count >= 0 ), EINVAL, NULL);  
   _VALIDATE_RETURN(( str != NULL ), EINVAL, NULL);  
  
   if (count == 0)  
   {  
       return NULL;  
   }  
  
   /* The C Standard states the input buffer should remain
   unchanged if EOF is encountered immediately. Hence we
   do not blank out the input buffer here */  
  
   /* Init stream pointer */  
   stream = str;  
  
   _lock_str(stream);  
   __try {  
#ifndef _UNICODE  
        _VALIDATE_STREAM_ANSI_SETRET(stream,EINVAL, retval, NULL);  
#endif /* _UNICODE */  
       if (retval!=NULL)  
       {  
           while (--count)  
           {  
                if ((ch =_fgettc_nolock(stream)) == _TEOF)  
                {  
                    if (pointer == string){  
                                   retval=NULL;  
                                    gotodone;  
                    }  
  
                    break;  
                }  
  
                if ((*pointer++ = (_TSCHAR)ch)== _T('\n'))  
                    break;  
           }  
  
           *pointer = _T('\0');  
       }  
  
  
/* Common return */  
done: ;
   }  
   __finally {  
       _unlock_str(stream);  
   }  
  
   return(retval);  
}  
可以看出,它的调用过程中会先调用_lock_str
[cpp] view plaincopy
#define _lock_str(s)            _lock_file(s)  
_lock_file的内部实现:
[cpp] view plaincopy
void __cdecl _lock_file (  
       FILE *pf  
       )  
{  
       /*
        * The way the FILE (pointed to by pf) is locked depends on whether
        * it is part of _iob[] or not
        */  
       if ( (pf >= _iob) && (pf <= (&_iob[_IOB_ENTRIES-1])))  
       {  
           /*
            * FILE lies in _iob[] so the lock lies in _locktable[].
             */
           _lock( _STREAM_LOCKS + (int)(pf - _iob) );  
           /* We set _IOLOCKED to indicate we locked the stream */  
           pf->_flag |= _IOLOCKED;  
       }  
       else  
           /*
            * Not part of _iob[]. Therefore, *pf is a _FILEX and the
            * lock field of the struct is an initialized critical
            * section.
            */  
           EnterCriticalSection( &(((_FILEX *)pf)->lock) );  
}  
对于_lock函数:
[cpp] view plaincopy
void __cdecl _lock (  
       int locknum  
       )  
{  
  
       /*
        * Create/open the lock, if necessary
        */  
       if ( _locktable[locknum].lock == NULL ) {  
  
           if ( !_mtinitlocknum(locknum) )  
                _amsg_exit( _RT_LOCK );  
       }  
  
       /*
        * Enter the critical section.
        */  
  
       EnterCriticalSection( _locktable[locknum].lock );  
}  
可以看出,不管加锁的方式如何,它实际上还是会调用系统提供的原始api来进行排他访问,从而避免多线程访问共享资源可能导致读或者写出错的问题。
Q那么如何设置将使用单线程版本的c库或者多线程版本的c库?
A如下图,是vs2010设置使用多线程版本c库的截图:
按照微软的说法,从vs2005开始,单线程版本的C库就已经被移除,所以可以不用担心使用单线程版本C库导致问题了。如果使用的是VC6,依然可以设置使用单线程版本C库。如果使用IDE工具没找到,可以使用命令行工具寻找相关选项:
当然,grep需要cygwin的支持。
Q printf函数,它可以处理变参,内部会如何处理呢?
A参数入栈的原则很好地支撑了变参的处理。也就是说,当确定了参数中一个的地址,那么其他参数的地址是随着这个地址按照类型大小变动即可。如下:
[cpp] view plaincopy
#define va_start _crt_va_start  
  
#define va_end _crt_va_end  
_crt_va_start_crt_va_end的声明如下:
[cpp] view plaincopy
#define _crt_va_start(ap,v)  ( ap = (va_list)_ADDRESSOF(v) + _INTSIZEOF(v))  
  
#define _crt_va_end(ap)      ( ap = (va_list)0 )  
可以看出,va_start也就是获取了变参的首地址,而va_end也就是将操作变参的数据设置为0,来结束处理。当然,上面的宏定义是一种平台下特殊情况,不同平台下的定义会有所不同。顺便将_ADDRESSOF宏和_INTSIZEOF宏列出:
[cpp] view plaincopy
#ifdef __cplusplus  
#define _ADDRESSOF(v)   ( &reinterpret_cast(v) )  
#else
#define _ADDRESSOF(v)   ( &(v) )
#endif
[cpp] view plaincopy
#define _INTSIZEOF(n)   ( (sizeof(n) + sizeof(int) - 1) &~(sizeof(int) - 1) )  
上面的这个宏表示以sizeof(int)对齐的大小。
Q printf函数内部究竟调用了什么操作系统API?
A从表面分析,应该会调用系统控制台输出的API; 如下是用ida工具对于如下代码hello.c生成的可执行文件的分析:
[cpp] view plaincopy
#include   
#include   
  
int main()
{  
char ch = 'a';  
wchar_t wch = (wchar_t)ch;  
printf("%C\n", wch);  
return 0;   
}  
使用cl  hello.c编译成hello.exe.
可以确切看到内部调用的系统API名称。当然,使用hook WriteConsoleW函数的方式同样可以得出结论。
Q经常看到关于文件输入输出的符号stdin, stdout, stderr,它们究竟是什么?
A它们是FILE *类型的变量。如下定义:
[cpp] view plaincopy
#define stdin  (&__iob_func()[0])  
#define stdout (&__iob_func()[1])  
#define stderr (&__iob_func()[2])  
__iob_func定义如下:
[cpp] view plaincopy
/*
*FILE descriptors; preset for stdin/out/err (note that the __tmpnum field
* isnot initialized)
*/  
FILE _iob[_IOB_ENTRIES] = {  
       /* _ptr, _cnt, _base,  _flag,_file, _charbuf, _bufsiz */  
  
       /* stdin (_iob[0]) */  
  
       { _bufin, 0, _bufin, _IOREAD | _IOYOURBUF, 0, 0, _INTERNAL_BUFSIZ},  
  
       /* stdout (_iob[1]) */  
  
       { NULL, 0, NULL, _IOWRT, 1, 0, 0 },
  
       /* stderr (_iob[3]) */  
  
       { NULL, 0, NULL, _IOWRT, 2, 0, 0 },
  
};  
  
  
/* These functions are for enablingSTATIC_CPPLIB functionality */  
_CRTIMP FILE * __cdecl__iob_func(void)  
{  
   return _iob;  
}  
不同平台,甚至相同平台下不同环境,对于描述符、句柄、指针之类的名称含义理解不尽一致。所以,这里使用英文的方式来说明。FILE *C语言抽象出来的文件操作指针,而对于FILE结构内部的_file成员可以被看成是handle,它是整形数据,正如stdin对应于file handle 0 stdout对应于file handle 1stderr对应于file handle 2.
对于FILE *file handle, 可以使用filenofdopen来互相获取对应的数值。
另外,上面的代码是从_file.c头文件中摘录,stderr (_iob[3])应该是它的错误,应该为stderr (_iob[2]).
Q对于字符串处理函数strtok, 它是如何保存中间状态的?
A单线程版本的strtok函数,可以通过static变量保存中间处理的位置信息,使得后来的调用可以继续工作。对于多线程版本的strtok函数,这样就不行了。实际上,它是采用了TLS的方式来保存这些中间数据。如下:
[cpp] view plaincopy
struct _tiddata {  
   unsigned long   _tid;       /* thread ID */  
  
  
   uintptr_t _thandle;         /*thread handle */  
  
   int     _terrno;            /* errno value */  
   unsigned long   _tdoserrno; /*_doserrno value */  
   unsigned int    _fpds;      /* Floating Point data segment */  
   unsigned long   _holdrand;  /* rand() seed value */  
   char *      _token;         /* ptr to strtok() token */  
   wchar_t *   _wtoken;        /* ptr to wcstok() token */  
   unsigned char * _mtoken;    /* ptrto _mbstok() token */  
  
   /* following pointers get malloc'd at runtime */  
   char *      _errmsg;        /* ptr to strerror()/_strerror() buff*/  
   wchar_t *   _werrmsg;       /* ptr to _wcserror()/__wcserror() buff*/  
   char *      _namebuf0;      /* ptr to tmpnam() buffer */  
   wchar_t *   _wnamebuf0;     /* ptr to _wtmpnam() buffer */  
   char *      _namebuf1;      /* ptr to tmpfile() buffer */  
   wchar_t *   _wnamebuf1;     /* ptr to _wtmpfile() buffer */  
   char *      _asctimebuf;    /* ptr to asctime() buffer */  
   wchar_t *   _wasctimebuf;   /* ptr to _wasctime() buffer */  
   void *      _gmtimebuf;     /* ptr to gmtime() structure */  
   char *      _cvtbuf;        /* ptr to ecvt()/fcvt buffer */  
   unsigned char _con_ch_buf[MB_LEN_MAX];
                                /* ptr toputch() buffer */  
   unsigned short _ch_buf_used;   /*if the _con_ch_buf is used */  
  
   /* following fields are needed by _beginthread code */  
   void *      _initaddr;      /* initial user thread address */  
   void *      _initarg;       /* initial user thread argument */  
  
   /* following three fields are needed to support signal handling and
    * runtime errors */  
   void *      _pxcptacttab;   /* ptr to exception-action table */  
   void *      _tpxcptinfoptrs; /*ptr to exception info pointers */  
   int         _tfpecode;      /* float point exception code */  
  
   /* pointer to the copy of the multibyte character information used by
    * the thread */  
   pthreadmbcinfo  ptmbcinfo;  
  
   /* pointer to the copy of the locale informaton used by the thead*/  
   pthreadlocinfo  ptlocinfo;  
   int         _ownlocale;     /* if 1, this thread owns its own locale*/  
  
   /* following field is needed by NLG routines */  
   unsigned long   _NLG_dwCode;  
  
   /*
    * Per-Thread data needed by C++ Exception Handling
    */  
   void *      _terminate;     /* terminate() routine */  
   void *      _unexpected;    /* unexpected() routine */  
   void *      _translator;    /* S.E. translator */  
   void *      _purecall;      /* called when pure virtual happens*/  
   void *      _curexception;  /* current exception */  
   void *      _curcontext;    /* current exception context */  
   int         _ProcessingThrow; /*for uncaught_exception */  
   void *              _curexcspec;    /* for handling exceptions thrown fromstd::unexpected */  
#if defined (_M_IA64) || defined(_M_AMD64)  
   void *      _pExitContext;  
   void *      _pUnwindContext;  
   void *      _pFrameInfoChain;  
   unsigned __int64   _ImageBase;  
#if defined (_M_IA64)  
   unsigned __int64   _TargetGp;  
#endif /* defined (_M_IA64) */  
   unsigned __int64   _ThrowImageBase;  
   void *     _pForeignException;  
#elif defined (_M_IX86)  
   void *      _pFrameInfoChain;  
#endif /* defined (_M_IX86) */  
   _setloc_struct _setloc_data;  
  
   void *      _reserved1;     /* nothing */  
   void *      _reserved2;     /* nothing */  
   void *      _reserved3;     /* nothing */  
#ifdef _M_IX86  
   void *      _reserved4;     /* nothing */  
   void *      _reserved5;     /* nothing */  
#endif /* _M_IX86 */  
  
   int _cxxReThrow;        /* Set toTrue if it's a rethrown C++ Exception */
  
   unsigned long __initDomain;     /*initial domain used by _beginthread[ex] for managed function */  
};  
  
typedef struct _tiddata * _ptiddata;  
可以看到,里面有strtok函数中间状态需要保存的token指针位置;而,对于strtok的实现代码也能看出:
[cpp] view plaincopy
#ifdef _SECURE_VERSION  
#define _TOKEN *context  
#else /* _SECURE_VERSION */  
#define _TOKEN ptd->_token  
#endif /* _SECURE_VERSION */  
  
#ifdef _SECURE_VERSION  
char * __cdecl strtok_s (  
       char * string,  
       const char * control,  
       char ** context  
       )  
#else /* _SECURE_VERSION */  
char * __cdecl strtok (  
       char * string,  
       const char * control  
       )  
#endif /* _SECURE_VERSION */  
{  
       unsigned char *str;  
       const unsigned char *ctrl = control;
  
       unsigned char map[32];  
       int count;  
  
#ifdef _SECURE_VERSION  
  
       /* validation section */  
       _VALIDATE_RETURN(context != NULL, EINVAL, NULL);  
       _VALIDATE_RETURN(string != NULL || *context != NULL, EINVAL, NULL);  
       _VALIDATE_RETURN(control != NULL, EINVAL, NULL);  
  
       /* no static storage is needed for the secure version */  
  
#else /* _SECURE_VERSION */  
  
       _ptiddata ptd = _getptd();  
  
#endif /* _SECURE_VERSION */  
  
       /* Clear control map */  
       for (count = 0; count < 32; count++)
                map[count] = 0;  
  
       /* Set bits in delimiter table */
       do {  
                map[*ctrl >> 3] |= (1<< (*ctrl & 7));  
       } while (*ctrl++);  
  
       /* Initialize str */  
  
       /* If string is NULL, set str to the saved
        * pointer (i.e., continue breaking tokens out of the string
        * from the last strtok call) */  
       if (string)  
                str = string;  
       else  
                str = _TOKEN;  
  
       /* Find beginning of token (skip over leading delimiters). Note that
        * there is no token iff this loop sets str to point to the terminal
        * null (*str == '\0') */  
       while ( (map[*str >> 3] & (1 << (*str & 7)))&& *str )  
                str++;  
  
       string = str;  
  
       /* Find the end of the token. If it is not the end of the string,
        * put a null there. */  
       for ( ; *str ; str++ )  
                if ( map[*str >> 3] &(1 << (*str & 7)) ) {  
                        *str++ = '\0';  
                        break;  
                }  
  
       /* Update nextoken (or the corresponding field in the per-thread data
        * structure */  
       _TOKEN = str;  
  
       /* Determine if a token has been found. */  
       if ( string == str )  
                return NULL;  
       else  
                return string;  
}  
函数的最后, _TOKEN= str; 正是操作了tiddata.
如果希望得到tiddata是如何初始化的,查看_beginthreadex函数的源代码(部分)
[cpp] view plaincopy
/*
       * Allocate and initialize a per-thread data structure for the to-
       * be-created thread.
       */  
      if ( (ptd = (_ptiddata)_calloc_crt(1, sizeof(struct _tiddata))) == NULL)  
               goto error_return;  
  
      /*
       * Initialize the per-thread data
       */  
  
      _initptd(ptd, _getptd()->ptlocinfo);
  
      ptd->_initaddr = (void *) initialcode;  
      ptd->_initarg = argument;  
      ptd->_thandle = (uintptr_t)(-1);
Q断言判断的代码应该怎么写?
A它的核心就在于如何输出错误信息和结束程序。
[cpp] view plaincopy
#define assert(_Expression) (void)((!!(_Expression)) || (_wassert(_CRT_WIDE(#_Expression), _CRT_WIDE(__FILE__),__LINE__), 0) )  
_wassert中将会组装错误信息并输出,并进入结束程序状态。
xichen
2012-5-29 16:54:53


[ 本帖最后由 tiankai001 于 2012-12-15 07:58 编辑 ]
此帖出自单片机论坛
 
 
 

回复

6366

帖子

4929

TA的资源

版主

18
 
c()----小话c语言(18)
[Mac 10.7.1  Lion  Intel-based x64  gcc4.2.1  xcode4.2 ]
Q如何解决abs函数传入一个整形数最小值返回溢出的数?
[cpp] view plaincopy
#include   
#include   
#include   
#include   
  
#define PRINT_D(intValue)       printf(#intValue" is %d\n",(intValue));  
#define PRINT_STR(str)          printf(#str" is %s\n",(str));  
  
int main()
{  
   int ret = abs(INT_MIN);  
   PRINT_D(ret)  
   return 0;  
}  
输出:
[plain] view plaincopy
ret is -2147483648  
A  显然是溢出了。查看abs的源代码(可能实际会有区别:查看的是苹果开源代码libc-763.12):
[cpp] view plaincopy
int  
abs(j)
   int j;  
{  
   return(j < 0 ? -j : j);  
}  
显然,结果必然会溢出。一个解决方法,单独处理负数最大值的情况,且提高返回值类型最大值范围,如下:
[cpp] view plaincopy
unsigned abs(int n)  
{  
   if(n == INT_MIN)  
       return INT_MAX + 1U;  
   return (n < 0 ? -n : n);  
}  
Q对于随机数,如何产生?
A采用固定算法产生的随机数必然不能是真正意义上的随机,也称为伪随机数。不过再伪,有时也没关系了,上层觉得很像随机数就ok了。
    很多随机数都采用如下的算法:
[plain] view plaincopy
Rand_Number = (Rand_Seed * X + Y) modZ  
下面可以参考源代码:
[cpp] view plaincopy
void
srand(seed)
u_int seed;
{  
   next = seed;  
}  
[cpp] view plaincopy
static int
do_rand(unsigned long *ctx)  
{  
#ifdef USE_WEAK_SEEDING  
/*
*Historic implementation compatibility.
*The random sequences do not vary much with the seed,
*even with overflowing.
*/  
   return ((*ctx = *ctx * 1103515245 + 12345) % ((u_long)RAND_MAX +1));  
#else  /* !USE_WEAK_SEEDING */  
/*
*Compute x = (7^5 * x) mod (2^31 - 1)
*without overflowing 31 bits:
*     (2^31 - 1) = 127773 * (7^5) + 2836
*From "Random number generators: good ones are hard to find",
*Park and Miller, Communications of the ACM, vol. 31, no. 10,
*October 1988, p. 1195.
*/  
   long hi, lo, x;  
  
   /* Can't be initialized with 0, so use another value. */  
   if (*ctx == 0)  
       *ctx = 123459876;  
    hi = *ctx / 127773;  
   lo = *ctx % 127773;  
    x= 16807 * lo - 2836 * hi;  
   if (x < 0)  
       x += 0x7fffffff;  
   return ((*ctx = x) % ((u_long)RAND_MAX + 1));  
#endif /* !USE_WEAK_SEEDING */  
}  
  
int  
rand()
{  
   return (do_rand(&next));  
}  
Q c库内部出现的错误或者异常,如何让上层获取到?
A可以采用全局变量保存的方式或者保存在各个线程的TLS区域。采用全局变量在多线程的时候会出现问题,TLS可以很好地解决这个问题。另外,也可能在各个可能出现异常函数加上一个参数专门保存异常信息,当然这样会导致函数接口不简洁。下面是苹果对errno的实现:
[cpp] view plaincopy
extern int errno;  
int *__error(void) {  
   pthread_t self = pthread_self();  
   /* If we're not a detached pthread, just return the global errno */  
   if ((self == (pthread_t)0) || (self->sig != _PTHREAD_SIG)) {  
       return &errno;  
   }  
   return &self->err_no;  
}  
  
int cthread_errno(void) {  
       return *__error();  
}  
可以看出,每个线程均会有单独的错误信息,它是支持多线程的。下面附注pthread_t结构:
[cpp] view plaincopy
typedef struct _pthread  
{  
   long           sig;       /* Unique signature for this structure*/  
   struct __darwin_pthread_handler_rec *__cleanup_stack;  
   pthread_lock_t lock;          /*Used for internal mutex on structure */  
   uint32_t    detached:8,  
           inherit:8,  
           policy:8,  
           freeStackOnExit:1,  
           newstyle:1,  
           kernalloc:1,  
           schedset:1,  
           wqthread:1,  
           wqkillset:1,  
           pad:2;  
   size_t         guardsize;   /* size in bytes to guard stack overflow*/  
#if !defined(__LP64__)  
   int        pad0;        /* for backwards compatibility */  
#endif
   struct sched_param param;  
   uint32_t    cancel_error;  
#if defined(__LP64__)  
   uint32_t    cancel_pad; /* padvalue for alignment */  
#endif
   struct _pthread *joiner;  
#if !defined(__LP64__)  
   int     pad1;       /*for backwards compatibility */  
#endif
   void           *exit_value;  
   semaphore_t    death;       /* pthread_join() uses this to wait fordeath's call */  
   mach_port_t    kernel_thread; /*kernel thread this thread is bound to */
    void           *(*fun)(void*);/* Thread startroutine */  
       void           *arg;          /* Argment for thread start routine*/  
   int        cancel_state;  /* Whether thread can be cancelled */  
   int        err_no;      /* thread-local errno */  
   void          *tsd[_EXTERNAL_POSIX_THREAD_KEYS_MAX +_INTERNAL_POSIX_THREAD_KEYS_MAX];  /*Thread specific data */  
       void           *stackaddr;     /* Base of the stack (is aligned onvm_page_size boundary */  
       size_t         stacksize;      /* Size of the stack (is a multiple ofvm_page_size and >= PTHREAD_STACK_MIN) */
   mach_port_t    reply_port;     /* Cached MiG reply port */  
#if defined(__LP64__)  
       int     pad2;       /* for natural alignment */  
#endif
    void          *cthread_self;  /* cthread_self()if somebody calls cthread_set_self() */  
   /* protected by list lock */  
   uint32_t    childrun:1,  
           parentcheck:1,  
           childexit:1,  
           pad3:29;  
#if defined(__LP64__)  
   int     pad4;       /* for natural alignment */  
#endif
   TAILQ_ENTRY(_pthread) plist;  
   void *  freeaddr;  
   size_t  freesize;  
   mach_port_t joiner_notify;  
   char   pthread_name[MAXTHREADNAMESIZE];       /* including nulll the name */  
       int max_tsd_key;  
   void *  cur_workq;  
   void * cur_workitem;  
   uint64_t thread_id;  
} *pthread_t;  
Q atexit函数该如何实现?
A需要一个可以保存数个注册的函数指针的结构,当应用程序结束前可以从中取出函数,按特定的顺序执行。
[cpp] view plaincopy
struct atexit {  
   struct atexit *next;            /*next in list */  
   int ind;                /* next index in this table*/  
   struct atexit_fn {  
       int fn_type;            /*ATEXIT_? from above */  
       union {  
           void (*std_func)(void);  
           void (*cxa_func)(void *);  
#ifdef __BLOCKS__  
           void (^block)(void);  
#endif /* __BLOCKS__ */  
       } fn_ptr;           /* functionpointer */  
       void *fn_arg;           /*argument for CXA callback */  
       void *fn_dso;           /* shared module handle */  
    }fns[ATEXIT_SIZE];         /* the tableitself */  
};  
当然,atexit是以进程为单位的,可以使用static变量保存此结构:
[cpp] view plaincopy
static struct atexit __atexit0;  
Q exit函数在何时调用atexit注册的函数呢?
A当然在调用系统退出应用程序前执行它。
[cpp] view plaincopy
/*
*Exit, flushing stdio buffers if necessary.
*/  
void
exit(status)  
   int status;  
{  
   __cxa_finalize(NULL);  
   if (__cleanup)  
       (*__cleanup)();  
   __exit(status);  
}  
__cxa_finalize函数就执行了atexit曾经注册的函数:
[cpp] view plaincopy
void
__cxa_finalize(void *dso)  
{  
   struct atexit *p;  
   struct atexit_fn fn;  
   int n;  
  
   _MUTEX_LOCK(&atexit_mutex);  
   for (p = __atexit; p; p = p->next) {
       for (n = p->ind;--n >= 0;) {  
           if (p->fns[n].fn_type == ATEXIT_FN_EMPTY)  
                continue; /* already beencalled */  
           if (dso != NULL && dso != p->fns[n].fn_dso)  
                continue; /* wrong DSO */  
           fn = p->fns[n];  
           /*
              Mark entry to indicate that thisparticular handler
              has already been called.
           */  
           p->fns[n].fn_type = ATEXIT_FN_EMPTY;
               _MUTEX_UNLOCK(&atexit_mutex);
         
           /* Call the function of correct type. */
           if (fn.fn_type == ATEXIT_FN_CXA)  
               fn.fn_ptr.cxa_func(fn.fn_arg);  
           else if (fn.fn_type == ATEXIT_FN_STD)
                fn.fn_ptr.std_func();  
           _MUTEX_LOCK(&atexit_mutex);  
       }  
   }  
   _MUTEX_UNLOCK(&atexit_mutex);
}  
Q system函数实现执行某个可执行文件,如何实现?
A采用fork方式创建一个子进程,如果返回父进程执行,将等待子进程完成;如果进入子进程,使用execl加载子进程镜像来执行。如下代码:
[cpp] view plaincopy
int  
__system(command)  
   const char *command;  
{  
   pid_t pid, savedpid;  
    int pstat;
   struct sigaction ign, intact, quitact;
   sigset_t newsigblock, oldsigblock;
  
   if (!command)       /* justchecking... */  
       return(1);  
  
   /*
    * Ignore SIGINT and SIGQUIT, block SIGCHLD. Remember to save
     * existing signal dispositions.
    */  
   ign.sa_handler = SIG_IGN;  
   (void)sigemptyset(&ign.sa_mask);
   ign.sa_flags = 0;  
   (void)_sigaction(SIGINT, &ign, &intact);  
   (void)_sigaction(SIGQUIT, &ign, &quitact);  
   (void)sigemptyset(&newsigblock);
   (void)sigaddset(&newsigblock, SIGCHLD);  
   (void)_sigprocmask(SIG_BLOCK, &newsigblock, &oldsigblock);  
   switch(pid = fork()) {  
   case -1:            /* error*/  
       break;  
   case 0:             /* child*/  
        /*
        * Restore original signal dispositions and exec the command.
        */  
       (void)_sigaction(SIGINT, &intact, NULL);  
       (void)_sigaction(SIGQUIT, &quitact, NULL);  
       (void)_sigprocmask(SIG_SETMASK, &oldsigblock, NULL);  
       execl(_PATH_BSHELL, "sh", "-c", command, (char*)NULL);  
       _exit(127);  
   default:            /* parent*/  
       savedpid = pid;  
       do {  
           pid = _wait4(savedpid, &pstat, 0, (struct rusage *)0);  
       } while (pid == -1 && errno == EINTR);  
       break;  
   }  
   (void)_sigaction(SIGINT, &intact, NULL);  
   (void)_sigaction(SIGQUIT, &quitact, NULL);  
   (void)_sigprocmask(SIG_SETMASK, &oldsigblock, NULL);  
   return(pid == -1 ? -1 : pstat);  
}  
xichen
2012-6-2 15:52:11


[ 本帖最后由 tiankai001 于 2012-12-15 07:59 编辑 ]
此帖出自单片机论坛
 
 
 

回复

6366

帖子

4929

TA的资源

版主

19
 
汇编和c只有一步之近----小话c语言(19)
作者:陈曦
日期:2012-6-810:50:13
环境:[Ubuntu11.04  Intel-based x64 gcc4.5.2  CodeBlocks10.05  AT&T汇编  Intel汇编]
转载请注明出处
Q举个例子吧。
A下面的代码的目标是计算1+2的值,最后放到变量temp中,并输出:
[cpp] view plaincopy
#include   
#include   
  
#define PRINT_D(longValue)       printf(#longValue" is %ld\n",((long)longValue));  
#define PRINT_STR(str)              printf(#str" is %s\n",(str));  
  
  
static void assemble_func()  
{  
   int temp;  
   __asm__("mov $1, %eax");
   __asm__("mov $2, %ebx");
   __asm__("add %ebx, %eax"); // 1 + 2  
   __asm__("mov %%eax, %0":"=r"(temp));    // mov the value of register eax to the var"temp"   
   PRINT_D(temp)               // print temp  
}  
  
int main()
{  
   assemble_func();  
   return 0;  
}  
运行结果:
[plain] view plaincopy
temp is 3
Q assemble_func函数的汇编代码形式是什么?
A
[cpp] view plaincopy
0x08048404 <+0>:   push   ebp  
  0x08048405 <+1>:   mov    ebp,esp
  0x08048407 <+3>:   push   ebx  
  0x08048408 <+4>:   sub    esp,0x24
=> 0x0804840b <+7>:    mov   eax,0x1  
  0x08048410 <+12>:  mov    ebx,0x2
  0x08048415 <+17>:  add    eax,ebx
  0x08048417 <+19>:  mov    ebx,eax
  0x08048419 <+21>:  mov    DWORD PTR [ebp-0xc],ebx  
  0x0804841c <+24>:  mov    eax,0x8048510  
  0x08048421 <+29>:  mov    edx,DWORD PTR [ebp-0xc]  
  0x08048424 <+32>:  mov    DWORD PTR [esp+0x4],edx  
  0x08048428 <+36>:  mov    DWORD PTR [esp],eax  
  0x0804842b <+39>:  call   0x8048340   
  0x08048430 <+44>:  add    esp,0x24
  0x08048433 <+47>:  pop    ebx  
  0x08048434 <+48>:  pop    ebp  
  0x08048435 <+49>:  ret      
上面的汇编是在调试运行到assemble_func函数的开始时,使用disassemble命令得到的数据。注意第五行左侧的箭头符号是调试状态显示正在运行的行数。
Q上面的汇编是内嵌到c代码中的,单独完全的汇编代码,如何实现hello world的功能?
A从本质上说,只用汇编的形式需要对于底层更了解,c代码从编译的角度来说和汇编没什么区别,只是写的格式以及调用的东西看起来不一致罢了。
如下,是实现标准控制台输出功能的代码:
[cpp] view plaincopy
.section .rodata  
str:
.ascii "Hello,world.\n"  
  
.section .text  
.globl _main  
_main:
movl $4,    %eax    # the number of system call   
movl $1,    %ebx    # file descriptor, 1 means stdout  
movl $str,  %ecx    # string address  
movl $13,   %edx    # string length  
int  $0x80  
保存为hello.s.
Q如何编译它,使用gcc吗?
A当然可以,不过这个文件显然不需要预处理了,它已经是汇编格式了,不需要单纯狭义的编译过程了,只需要从汇编过程开始了。
它可以直接生成目标文件hello.o
Q接下来做什么?可以直接执行它吗?
A试试。
此时,给hello.o添加可执行权限再执行:
Q这是为什么?
A继续观察hello.o文件的属性。
可以看出,它还不是可执行文件。其实很简单,hello.o只是目标文件,并没有链接成可执行文件。
Q这又是为什么?没有找到入口符号_start, ld默认的入口符号是_start?
A是的。在代码中使用的是_main, 所以应该让链接器明白,入口符号是_main.
Q现在应该可以运行了吧。运行一下:
Hello,world是输出了,为什么后面会出现段错误呢?
A我们首先看看上面的运行返回了什么。
返回值为139,它代表什么?
Q从系统的errno.h头文件以及相关文件中查找,得到所有系统错误码:
/usr/include/asm-generic/errno-base.h文件:
[cpp] view plaincopy
#ifndef _ASM_GENERIC_ERRNO_BASE_H  
#define _ASM_GENERIC_ERRNO_BASE_H  
  
#define EPERM        1 /* Operation not permitted */  
#define ENOENT       2 /* No such file or directory */  
#define ESRCH        3 /* No such process */  
#define EINTR        4 /* Interrupted system call */  
#define EIO      5 /* I/O error */  
#define ENXIO        6 /* No such device or address */  
#define E2BIG        7 /* Argument list too long */  
#define ENOEXEC      8 /* Exec format error */  
#define EBADF        9 /* Bad file number */  
#define ECHILD      10 /* No child processes */  
#define EAGAIN      11 /* Try again */  
#define ENOMEM      12 /* Out of memory */  
#define EACCES      13 /* Permission denied */  
#define EFAULT      14 /* Bad address */  
#define ENOTBLK     15 /* Block device required */  
#define EBUSY       16 /* Device or resource busy */  
#define EEXIST      17 /* File exists */  
#define EXDEV       18 /* Cross-device link */  
#define ENODEV      19 /* No such device */  
#define ENOTDIR     20 /* Not a directory */  
#define EISDIR      21 /* Is a directory */  
#define EINVAL      22 /* Invalid argument */  
#define ENFILE      23 /* File table overflow */  
#define EMFILE      24 /* Too many open files */  
#define ENOTTY      25  /*Not a typewriter */  
#define ETXTBSY     26 /* Text file busy */  
#define EFBIG       27 /* File too large */  
#define ENOSPC      28 /* No space left on device */  
#define ESPIPE      29 /* Illegal seek */  
#define EROFS       30 /* Read-only file system */  
#define EMLINK      31 /* Too many links */  
#define EPIPE       32 /* Broken pipe */  
#define EDOM        33 /* Math argument out of domain of func */  
#define ERANGE      34 /* Math result not representable */
  
#endif
/usr/include/asm-generic/errno.h文件:
[cpp] view plaincopy
#ifndef _ASM_GENERIC_ERRNO_H  
#define _ASM_GENERIC_ERRNO_H  
  
#include  
  
#define EDEADLK     35 /* Resource deadlock would occur */
#define ENAMETOOLONG    36  /*File name too long */  
#define ENOLCK      37 /* No record locks available */  
#define ENOSYS      38 /* Function not implemented */  
#define ENOTEMPTY   39  /*Directory not empty */  
#define ELOOP       40 /* Too many symbolic links encountered */  
#define EWOULDBLOCK EAGAIN  /* Operation would block */  
#define ENOMSG      42 /* No message of desired type */  
#define EIDRM       43 /* Identifier removed */  
#define ECHRNG      44 /* Channel number out of range */
#define EL2NSYNC    45  /* Level 2 not synchronized */  
#define EL3HLT      46 /* Level 3 halted */  
#define EL3RST      47 /* Level 3 reset */  
#define ELNRNG      48 /* Link number out of range */  
#define EUNATCH     49 /* Protocol driver not attached */
#define ENOCSI      50 /* No CSI structure available */  
#define EL2HLT      51 /* Level 2 halted */  
#define EBADE       52 /* Invalid exchange */  
#define EBADR       53 /* Invalid request descriptor */  
#define EXFULL      54 /* Exchange full */  
#define ENOANO      55 /* No anode */  
#define EBADRQC     56 /* Invalid request code */  
#define EBADSLT     57 /* Invalid slot */  
  
#define EDEADLOCK   EDEADLK
  
#define EBFONT      59 /* Bad font file format */  
#define ENOSTR      60 /* Device not a stream */  
#define ENODATA     61 /* No data available */  
#define ETIME       62 /* Timer expired */  
#define ENOSR       63 /* Out of streams resources */  
#define ENONET      64 /* Machine is not on the network */
#define ENOPKG      65 /* Package not installed */  
#define EREMOTE     66 /* Object is remote */  
#define ENOLINK     67 /* Link has been severed */  
#define EADV        68 /* Advertise error */  
#define ESRMNT      69 /* Srmount error */  
#define ECOMM       70 /* Communication error on send */
#define EPROTO      71 /* Protocol error */  
#define EMULTIHOP   72  /*Multihop attempted */  
#define EDOTDOT     73 /* RFS specific error */  
#define EBADMSG     74 /* Not a data message */  
#define EOVERFLOW   75  /*Value too large for defined data type */
#define ENOTUNIQ    76 /* Name not unique on network */  
#define EBADFD      77 /* File descriptor in bad state */
#define EREMCHG     78 /* Remote address changed */  
#define ELIBACC     79 /* Can not access a needed shared library */  
#define ELIBBAD     80 /* Accessing a corrupted shared library */  
#define ELIBSCN     81 /* .lib section in a.out corrupted */
#define ELIBMAX     82 /* Attempting to link in too many shared libraries */  
#define ELIBEXEC    83 /* Cannot exec a shared library directly */  
#define EILSEQ      84 /* Illegal byte sequence */  
#define ERESTART    85 /* Interrupted system call should be restarted */  
#define ESTRPIPE    86 /* Streams pipe error */  
#define EUSERS      87 /* Too many users */  
#define ENOTSOCK    88 /* Socket operation on non-socket */
#define EDESTADDRREQ    89 /* Destination address required */
#define EMSGSIZE    90 /* Message too long */  
#define EPROTOTYPE  91  /*Protocol wrong type for socket */  
#define ENOPROTOOPT 92  /* Protocol not available */  
#define EPROTONOSUPPORT 93  /* Protocol not supported */  
#define ESOCKTNOSUPPORT 94  /* Socket type not supported */  
#define EOPNOTSUPP  95  /*Operation not supported on transport endpoint */  
#define EPFNOSUPPORT    96 /* Protocol family not supported */
#define EAFNOSUPPORT    97 /* Address family not supported by protocol */  
#define EADDRINUSE  98  /*Address already in use */  
#define EADDRNOTAVAIL   99  /*Cannot assign requested address */  
#define ENETDOWN    100 /* Network is down */  
#define ENETUNREACH 101 /* Network isunreachable */  
#define ENETRESET   102 /* Network dropped connection because ofreset */  
#define ECONNABORTED    103 /* Software caused connection abort*/  
#define ECONNRESET  104 /* Connection reset by peer */  
#define ENOBUFS     105 /* No buffer space available */  
#define EISCONN     106 /* Transport endpoint is alreadyconnected */  
#define ENOTCONN    107 /* Transport endpoint is not connected*/  
#define ESHUTDOWN   108 /* Cannot send after transport endpointshutdown */  
#define ETOOMANYREFS    109 /* Too many references: cannot splice*/  
#define ETIMEDOUT   110 /* Connection timed out */  
#define ECONNREFUSED    111 /* Connection refused */  
#define EHOSTDOWN   112 /* Host is down */  
#define EHOSTUNREACH    113 /* No route to host */  
#define EALREADY    114 /* Operation already in progress*/  
#define EINPROGRESS 115 /* Operation now inprogress */  
#define ESTALE      116 /* Stale NFS file handle */  
#define EUCLEAN     117 /* Structure needs cleaning */  
#define ENOTNAM     118 /* Not a XENIX named type file */  
#define ENAVAIL     119 /* No XENIX semaphores available*/  
#define EISNAM      120 /* Is a named type file */  
#define EREMOTEIO   121 /* Remote I/O error */  
#define EDQUOT      122 /* Quota exceeded */  
  
#define ENOMEDIUM   123 /* No medium found */  
#define EMEDIUMTYPE 124 /* Wrong mediumtype */  
#define ECANCELED   125 /* Operation Canceled */  
#define ENOKEY      126 /* Required key not available */  
#define EKEYEXPIRED 127 /* Key has expired*/  
#define EKEYREVOKED 128 /* Key has beenrevoked */  
#define EKEYREJECTED    129 /* Key was rejected by service */  
  
/* for robust mutexes */  
#define EOWNERDEAD  130 /* Owner died */  
#define ENOTRECOVERABLE 131 /* State notrecoverable */  
  
#define ERFKILL     132 /* Operation not possible due toRF-kill */  
  
#endif
就是没有找到139.
A看来,系统已经发生一些诡异的情况,错误码已经不正确了。为了确定139错误码确实不存在,我们在/usr/include目录下递归搜索139这个字符。
[plain] view plaincopy
grep -R '139' *  
结果比较长,这里不列出来来。依然没有能找到系统对应的139错误定义。
那么,我们来看看系统日志吧,到底哪里可能有问题。
Q使用如下命令得到了错误信息:
最后的地方确实看到hello应用程序运行错误的系统日志。应该是指针访问出错。原因是否是汇编代码大最后没有恰当地设置堆栈寄存器等寄存器的值呢?
A在这里,很有可能。为了更容易看出问题可能在哪里,写一个类似功能的c代码,得到它的汇编代码,和上面的汇编代码进行比较。
Q写了如下的hello_1.c代码如下:
[cpp] view plaincopy
#include   
  
int main()
{  
   printf("Hello,world!\n");
   return 0;  
}  
查看它的汇编代码:
[cpp] view plaincopy
   .file   "hello_1.c"  
   .section    .rodata  
.LC0:
    .string "Hello,world!"  
   .text  
.globl main
   .type   main, @function  
main:
   pushl   %ebp  
   movl    %esp, %ebp  
   andl    $-16, %esp  
   subl    $16, %esp  
   movl    $.LC0, (%esp)  
   call    puts  
   movl    $0, %eax  
    leave  
   ret  
   .size   main, .-main  
   .ident  "GCC: (Ubuntu/Linaro4.5.2-8ubuntu4) 4.5.2"  
   .section   .note.GNU-stack,"",@progbits
果然,和hello.s代码确实有不一样。这里,开始执行时对ebp, esp进行了处理,最后使用了leaveret命令。就是它们引起的吗?
A不过在实际中,不管是加入pushl  %ebp之类代码,还是加入leave, ret指令,最终执行依然是段错误。这个地方笔者一直没明白,如果有谁知道的,希望能不吝赐教。不过,可以调用exit系统调用实现结束应用程序,这样就不会出现段错误。如下:
[cpp] view plaincopy
.section .rodata  
str:
.ascii "Hello,world.\n"  
  
.section .text  
.globl _main  
_main:
  
movl $4,    %eax    # the number of system call   
movl $1,    %ebx    # file descriptor, 1 means stdout  
movl $str,  %ecx    # string address  
movl $13,   %edx    # string length  
int  $0x80  
  
movl $1,    %eax  
movl $0,    %ebx  
int  $0x80  
运行结果:
Q进行0x80软中断进行系统调用,参数在哪里保存,就在上面写的寄存器里面吗?
A是的。linux下,功能号和返回值在eax中保存,参数一般在5个以下,就按照ebx, ecx, edx, esi, edi来传递,如果参数过多,就会使用堆栈。可以看到上面两次系统调用,均是在使用ebx,ecx, edx这些寄存器。
Q 4号系统调用是什么?在哪里能知道?
A可以在/usr/include/asm/unistd_32.h或者/usr/include/asm/unistd_64.h中看到平台所有系统调用,下面为unistd_32.h文件中开始一部分:
[cpp] view plaincopy
#define __NR_restart_syscall      0  
#define __NR_exit         1
#define __NR_fork         2
#define __NR_read         3
#define __NR_write        4
#define __NR_open         5
#define __NR_close        6
#define __NR_waitpid          7
#define __NR_creat        8
#define __NR_link         9
#define __NR_unlink      10
#define __NR_execve      11
#define __NR_chdir       12
#define __NR_time        13
#define __NR_mknod       14
#define __NR_chmod       15
#define __NR_lchown      16
#define __NR_break       17
可以看到,1号系统调用为exit, 4号为write, 正是上面代码使用的。
Q汇编如何调用c库函数?
A使用call指令,不过调用前要传好参数。如下代码,调用cprintf函数:
[cpp] view plaincopy
.section .rodata  
str:
.ascii "Hello,world.\n"  
  
.section .text  
.globl main
main:
  
pushl  $str  
call   printf  
  
pushl  $0  
call   exit  
保存为printf.s, 编译:
运行:
Q可以使用as, ld来汇编以及链接吗?
A可以的。不过需要注意,因为它使用c库,需要指定链接c: -lc;
Q  乘法运算mul后面只跟着一个数,另一个数存哪里?
A另一个数存储在al, ax或者eax寄存器中,这取决于使用的是mulb, mulw还是mull指令。结果将按照高位到地位的顺序保存在dxax中。
同理,除法运算div后面也只跟一个除数,被除数保存在ax dx:ax或者edx:eax中。除数的最大长度只能是被除数的一半。商和余数将根据被除数占用大小来确定:
如果被除数在ax中,商在al, 余数在ah; 如果被除数在eax中,商在ax, 余数在dx; 如果被除数在edx:eax中,商在eax, 余数在edx.
如下是测试代码:
[cpp] view plaincopy
#include   
#include   
  
#define PRINT_D(longValue)       printf(#longValue" is %ld\n",((long)longValue));  
#define PRINT_STR(str)              printf(#str" is %s\n",(str));  
  
  
static void assemble_func()  
{  
   int result_high, result_low;  
   short result, remainder;  
  
   //mul  
   __asm__("mov $10, %eax");
   __asm__("mov $10, %ebx");
   __asm__("mull %ebx");  
   __asm__("mov %%edx, %0":"=r"(result_high));  
   __asm__("mov %%eax, %0":"=r"(result_low));  
   PRINT_D(result_high)  
   PRINT_D(result_low)  
  
   // div  
   __asm__("mov $0,  %dx");  
   __asm__("mov $100, %ax");  // the divident is dx:ax  
   __asm__("mov $9, %bx");  
   __asm__("div %bx");        // the divisor is bx  
   __asm__("movw %%ax, %0":"=r"(result));  
   __asm__("movw %%dx, %0":"=r"(remainder));  
   PRINT_D(result)  
   PRINT_D(remainder)  
}  
  
int main()
{  
   assemble_func();  
   return 0;  
}  
输出结果:
[plain] view plaincopy
result_high is 0  
result_low is 100  
result is 11  
remainder is 1  
Q  对于数据比较指令cmp,它是如何配合jmp相关的指令?
A  cmp指令将进行两个数据的差计算,如果得到的是0,jz成立; 如果不是0, jnz成立。如下例子:
[cpp] view plaincopy
#include   
#include   
  
#define PRINT_D(longValue)      printf(#longValue" is %ld\n",((long)longValue));  
#define PRINT_STR(str)          printf(#str" is %s\n",(str));  
#define PRINT(str)              printf(#str"\n");  
  
  
static void assemble_func()  
{  
   __asm__("mov $10, %eax");
   __asm__("cmp $10, %eax ");
   __asm__("jz  end");  
   PRINT("below jz")  
   __asm__("end:");  
   PRINT("the end")  
  
}  
  
int main()
{  
   assemble_func();  
   return 0;  
}  
显然,jz会成立,输出如下:
[plain] view plaincopy
"the end"  
Q对于某些时候,加法可能导致溢出,如何判断出来?
A CPU内部有一个寄存器,它内部会保存溢出标志位OF, 可以通过jo或者jno判断。
[cpp] view plaincopy
#include   
#include   
  
#define PRINT_D(longValue)      printf(#longValue" is %ld\n",((long)longValue));  
#define PRINT_STR(str)          printf(#str" is %s\n",(str));  
#define PRINT(str)              printf(#str"\n");  
  
  
static void assemble_func()  
{  
   __asm__("movw   $0x7FFF,  %ax");
   __asm__("movw   $0x7FFF,  %bx");
   __asm__("addw   %bx,      %ax");  
  
   __asm__("jo    overflow_set");  
  
   __asm__("movl   $1,       %eax");  
   __asm__("movl   $0,       %ebx");  
   __asm__("int   $0x80");  
  
   __asm__("overflow_set:");
   PRINT("overflow flag is set...")  
}  
  
int main()
{  
   assemble_func();  
   return 0;  
}  
运行结果:
[plain] view plaincopy
"overflow flag is set..."  
Q对于溢出,到底应该判断?
A以加法举例,如果两个相同符号的数相加得到的结果符号相反,那么一定溢出了。
Q OFCF标志位有什么区别?
A CF代表进位标志。进位不一定是溢出,比如有符号整形最小值加1,虽然进位,但是没溢出。因为计算机补码的理论允许进位,但是结果却正确。
[cpp] view plaincopy
#include   
#include   
  
#define PRINT_D(longValue)      printf(#longValue" is %ld\n",((long)longValue));  
#define PRINT_STR(str)          printf(#str" is %s\n",(str));  
#define PRINT(str)              printf(#str"\n");  
  
  
static void assemble_func()  
{  
   __asm__("movw   $0xFFFF,  %ax");
   __asm__("movw   $0x1,  %bx");
   __asm__("addw   %bx,      %ax");  
  
   __asm__("je    carry_set");  
  
   __asm__("movl   $1,       %eax");  
   __asm__("movl   $0,       %ebx");  
   __asm__("int   $0x80");  
  
   __asm__("carry_set:");  
   PRINT("carry flag is set...")
}  
  
int main()
{  
   assemble_func();  
   return 0;  
}  
运行结果:
[plain] view plaincopy
"carry flag is set..."  
当然,我们可以用jo来测试上面的加法是否溢出。
[cpp] view plaincopy
#include   
#include   
  
#define PRINT_D(longValue)      printf(#longValue" is %ld\n",((long)longValue));  
#define PRINT_STR(str)          printf(#str" is %s\n",(str));  
#define PRINT(str)              printf(#str"\n");  
  
  
static void assemble_func()  
{  
   __asm__("movw   $0xFFFF,  %ax");
   __asm__("movw   $0x1,  %bx");
   __asm__("addw   %bx,      %ax");  
  
   __asm__("jo    overflow_set");  
  
   __asm__("movl   $1,       %eax");  
   __asm__("movl   $0,       %ebx");  
   __asm__("int   $0x80");  
  
   __asm__("overflow_set:");
   PRINT("overflow flag is set...")  
}  
  
int main()
{  
   assemble_func();  
   return 0;  
}  
执行结果:
它什么也没输出,这就意味着OF没有被置位。
作者:陈曦
日期:2012-6-810:50:13
环境:[Ubuntu11.04  Intel-based x64 gcc4.5.2  CodeBlocks10.05  AT&T汇编 Intel汇编]
转载请注明出处
:#32�=3>�2查看某个函数的汇编形式,怎么办?
A: 可以使用-p参数来指定查看的函数。如下:
file:///C:/DOCUME~1/ADMINI~1/LOCALS~1/Temp/msohtml1/01/clip_image020.gif
上面查看了hello中的main函数的汇编形式(注意,虽然代码中是main函数,但是在可执行文件的符号表中,可能会被改变,这依赖编译器).
Q: 有的可执行文件支持不止一种硬件架构的代码,如何查看包含哪些?
A: 可以使用file命令查看。如上,查看上面的hello文件的信息:
file:///C:/DOCUME~1/ADMINI~1/LOCALS~1/Temp/msohtml1/01/clip_image022.gif
可以看到它是64位运行于x64平台上的可执行文件。
Q: 如何编译得到一个可执行文件,它是通用的,可以包含几种架构代码的?
A: 使用gcc的-arch参数功能来实现。依然使用上面的hello.c代码:
file:///C:/DOCUME~1/ADMINI~1/LOCALS~1/Temp/msohtml1/01/clip_image024.gif
再运行file命令:
file:///C:/DOCUME~1/ADMINI~1/LOCALS~1/Temp/msohtml1/01/clip_image026.gif
可以看到,文件包含了两种架构体系代码。
Q: 看到很多逆向工具,可以修改特定的目标文件实现特定的目的,是否可以修改hello文件来得到希望的执行效果?
A: 是的。我并不善于修改汇编代码,我们可以修改数据段来查看效果。
修改上面的hello.c代码如下:
1.     #include   
2.      
3.     int g_i = 0xAA;  
4.     const int g_j = 0xBB;  
5.     char *str = "hello";  
6.      
7.     int main()  
8.     {  
9.         int i = 2;  
10.      printf("%s %d\n", str, i);  
11.      return 0;  
12.  }  


编译成hello.并查看执行效果:
file:///C:/DOCUME~1/ADMINI~1/LOCALS~1/Temp/msohtml1/01/clip_image028.gif
下面就使用vi来修改可执行文件的hello字符串为iello,最后查看执行效果。使用vi打开hello文件,定位到hello字符串的位置:
file:///C:/DOCUME~1/ADMINI~1/LOCALS~1/Temp/msohtml1/01/clip_image030.gif
修改hello的h为i:
file:///C:/DOCUME~1/ADMINI~1/LOCALS~1/Temp/msohtml1/01/clip_image032.gif
保存,然后执行hello:
file:///C:/DOCUME~1/ADMINI~1/LOCALS~1/Temp/msohtml1/01/clip_image034.gif
可以看到,执行结果已经变了。
Q: 有时,需要查看一个目标文件中各个段的大小,如何查看?
A: 可以使用size命令。
file:///C:/DOCUME~1/ADMINI~1/LOCALS~1/Temp/msohtml1/01/clip_image036.gif
Q: 有时,需要查看目标文件中可打印的字符串有哪些,如何查看?
A: 可以使用strings命令。
file:///C:/DOCUME~1/ADMINI~1/LOCALS~1/Temp/msohtml1/01/clip_image038.gif
Q: 很多编译好的程序,里面包含了符号表或者一些调试信息,如何去除它们?
A: 可以使用strip命令。
file:///C:/DOCUME~1/ADMINI~1/LOCALS~1/Temp/msohtml1/01/clip_image040.gif
使用strip命令后:
file:///C:/DOCUME~1/ADMINI~1/LOCALS~1/Temp/msohtml1/01/clip_image042.gif
可以看到,符号信息确实少了。
Q: 上面提到的nm命令,它是做什么的?
A: 它是可以查看目标文件中包含的符号信息的。
xichen
2012-5-2512:50:52


[ 本帖最后由 tiankai001 于 2012-12-15 07:59 编辑 ]
此帖出自单片机论坛
 
 
 

回复

6366

帖子

4929

TA的资源

版主

20
 
要理解解释器,做一个小解释器----小话c语言(20)
作者:陈曦
日期:2012-6-12   11:31:12
环境:[Mac10.7.1  Lion  Intel-based x64  gcc4.2.1  xcode4.2]
转载请注明出处
Q解释器来源于什么?
A如果说是广义的解释器,那么可以把它理解成翻译器,只要能将一种被看成原始的东西翻译成需要的东西,处理的东西就可以被称为解释器。从编程语言角度,解释器更多地表达的含义是,将一种初始状态的数据(一般是文本)转换成另外一种通常来说是较为容易理解的文本或者一种执行过程。
     解释器正来源于自然语言的机器不容易理解性。
Q我们尝试做个c语言解释器吧。
A  罗马也不是一日建成的。我们先做个简单的,这个简单的解释器简单地让我们觉得可以不用做它,但是,我们还是要做它。
    它主要实现以下几个简单的功能:
    解释器名称为simple_interpreter, 命令行下执行它将运行解释器;
    1、输入hello后,它会提示文本: hello, i am a interpreter!
    2、输入ver后,它会提示文本: version: 1.0
    3、输入print  [字符串] 它会输出对应的字符串,字符串不需要任何分界符
    4、输入exit或者quit后,解释器将关闭
    5、如果输入其它命令,输出no such command
Q下面的代码是根据上面的需求做出的。
[cpp] view plaincopy
#include   
#include   
#include   
#include "str_process.h"  
  
int main (int argc, const char *argv[])  
{  
   char    buf[4096] = {0};  
   size_t  buf_size =sizeof(buf);  
   char*   ret;  
     
   // input from stdin  
   while (1)  
   {  
       ret = fgets((char *)&buf, (int)buf_size, stdin);  
       if(ret == NULL) // error or eof  
       {  
           if(ferror(stdin))  
                printf("erroroccurs...terminating now...\n");  
           else if(feof(stdin))  
                printf("eofoccurs...terminating now...\n");  
       }  
       else    // input sth  
       {  
           // set the last '\n' to NULL  
           if(buf[strlen(buf) - 1] == '\n')  
                buf[strlen(buf) - 1] = '\0';  
              
           if(!strcmp(buf, "hello"))
           {  
                printf("hello, i am ainterpreter!\n");  
           }  
           else if(!strcmp(buf, "ver"))
           {  
               printf("version:1.0\n");
           }  
           else if(!strncmp(buf, "print",strlen("print")))  
           {  
                char* temp = buf +strlen("print");  
                if(*temp == ' ')  
                {  
                   cc_skip_blank(&temp);     
                   printf("%s\n",temp);  
                }  
                else if(*temp == '\0')  
                {  
                   printf("\n");   
                }  
                else  
                {  
                    printf("no suchcommand\n");  
                }  
           }  
           else if(!strcmp(buf, "exit") || !strcmp(buf,"quit"))  
           {  
                exit(0);  
           }  
           else  
           {  
                printf("no suchcommand\n");  
            }
       }  
   }  
     
   return 0;  
}  
保存为simple_interpreter.c
str_process.hstr_process.c代码如下:
[cpp] view plaincopy
#ifndef CCSH_STR_PROCESS_H  
#define CCSH_STR_PROCESS_H  
#include   
  
bool   cc_is_blank(char ch);  
char   *cc_get_next_blank(const char *str);
void   cc_skip_blank(char  **str);  
bool   cc_str_is(const char *str1, const char *str2);  
bool   cc_str_begin_with(const char *str, char ch);  
  
#endif
[cpp] view plaincopy
#include   
#include "str_process.h"  
#include   
  
inline bool    cc_is_blank(char ch)  
{  
   return ch == '\n'   
       || ch == '\t'   
       || ch == ' ';  
}  
  
char *cc_get_next_blank(const char*str)  
{  
   while (!cc_is_blank(*str) && *str != '\0')  
       ++str;  
   return (char *)str;  
}  
  
void   cc_skip_blank(char  **str)  
{  
   while(cc_is_blank(**str) && *str != '\0')  
   {  
       (*str)++;  
   }  
}  
  
bool   cc_str_is(const char *str1, const char *str2)  
{  
    return strcmp(str1, str2) == 0;  
}  
  
inline bool    cc_str_begin_with(const char *str, charch)  
{  
   return str[0] == ch;  
}  
工程生成simple_interpreter,运行:
A是的,上面的代码可以按要求执行。不过,它有它的缺点,一是格式固定的太死,如果多输入一个空格,可能造成no such command的错误; 二是它进行不同输入处理的代码过为集中,如果再增加一些,代码维护可能有很大的问题;三是上面的输入输出没有一个标志,很容易混淆。
Q如果需要解决第一个问题,那么需要对输入的文本进行初步解析,去除空格、TAB等信息,留下有用的信息;如果对第二个问题,可以用不同文件的单独函数来处理不同的条件分支;第三个问题,增加一个提示符,类似$.
A对于第一个问题,可采用如下的图示:
Q将缓冲区数据转换成参数列表,代码如下:
arglist.h:
[cpp] view plaincopy
#ifndef CCSH_ARGLIST_H  
#define CCSH_ARGLIST_H  
  
typedef struct _cc_arg_obj  
{  
   char    *str;  
   size_t  len;  
   struct  _cc_arg_obj    *next;
   char    *buf_pointer;       // the pointer that points the buf, forpossible use, eg. echo command  
}cc_arg_obj;  
  
typedef struct _cc_arg_list  
{  
   cc_arg_obj             *head;  
   cc_arg_obj             *tail;  
}cc_arg_list;  
  
  
cc_arg_obj *cc_arg_obj_make(const char *str,  
                             size_t len,   
                             cc_arg_obj *next,  
                             char       *buf_pointer);  
void       cc_arg_obj_free(cc_arg_obj *obj);
  
cc_arg_list*cc_arg_list_make(cc_arg_obj   *head);  
cc_arg_obj *cc_arg_list_append(cc_arg_list*list, cc_arg_obj     *obj);  
void       cc_arg_list_free(cc_arg_list   *list);  
void       cc_arg_list_show_all_args(cc_arg_list  *list);  
  
#endif
arglist.c:
[cpp] view plaincopy
#include   
#include "arglist.h"  
#include "common.h"  
#include "error.h"  
  
cc_arg_obj *cc_arg_obj_make(const char *str,  
                             size_t len,   
                             cc_arg_obj*next,  
                             char       *buf_pointer)  
{  
   cc_arg_obj  *obj = (cc_arg_obj*)malloc(sizeof(cc_arg_obj));  
   if(!obj)  
   {  
       cc_err(CC_ERR_NOMEM);  
       return NULL;  
   }  
   char *obj_str = (char *)malloc(len + 1);
   if(!obj_str)  
   {  
       cc_err(CC_ERR_NOMEM);  
       free(obj);  
       return NULL;  
    }  
   strncpy(obj_str, str, len);  
   obj->str = obj_str;  
   obj->len = len;  
   obj->next = next;  
   obj->buf_pointer = buf_pointer;
   return obj;  
}  
  
  
void       cc_arg_obj_free(cc_arg_obj *obj)  
{  
   free(obj->str);  
   free(obj);  
}  
  
  
cc_arg_list*cc_arg_list_make(cc_arg_obj   *head)  
{  
   cc_arg_list *list = (cc_arg_list *)malloc(sizeof(cc_arg_list));  
   if(!list)  
   {  
       cc_err(CC_ERR_NOMEM);  
       return NULL;  
   }  
     
   list->head = list->tail = head;
   return list;  
}  
  
cc_arg_obj *cc_arg_list_append(cc_arg_list*list, cc_arg_obj     *obj)  
{  
   if(list->head == NULL)  
   {  
       list->head = list->tail = obj;
       return obj;  
   }  
   list->tail->next = obj;  
   list->tail = obj;  
   return obj;  
}  
  
void       cc_arg_list_free(cc_arg_list   *list)  
{  
   cc_arg_obj *head = list->head;
   while(head)  
   {  
       cc_arg_obj *next = head->next;
       cc_arg_obj_free(head);  
        head = next;
   }  
}  
  
void       cc_arg_list_show_all_args(cc_arg_list  *list)  
{  
   cc_arg_obj *head = list->head;
   while (head != NULL)  
   {  
       printf("arg:%s", head->str);  
       head = head->next;  
   }  
}  
buf_to_arglist.h:
[cpp] view plaincopy
#ifndef CCSH_BUF_TO_ARGLIST_H  
#define CCSH_BUF_TO_ARGLIST_H  
  
#include "arglist.h"  
  
cc_arg_list *cc_buf_to_arglist(const char*buf);  
  
  
#endif
buf_to_arglist.c:
[cpp] view plaincopy
#include   
#include "buf_to_arglist.h"  
#include   
#include "error.h"  
#include "str_process.h"  
  
cc_arg_list *cc_buf_to_arglist(const char*buf)  
{  
   char    *temp = (char *)buf;  
   cc_arg_list *list = cc_arg_list_make(NULL);  
   if(!list)  
   {  
       cc_err(CC_ERR_NOMEM);  
       return NULL;  
   }  
   while (*temp)  
   {  
       char    *next_blank =cc_get_next_blank(temp);  
       if(temp != next_blank)  
       {  
           size_t len = next_blank - temp;  
           cc_arg_obj *obj =cc_arg_obj_make(temp, len, NULL, temp);  
           if(!obj)  
           {  
                cc_err(CC_ERR_NOMEM);  
                cc_arg_list_free(list);  
                return NULL;  
           }  
           cc_arg_list_append(list, obj);  
       }  
       temp = next_blank;  
       cc_skip_blank(&temp);  
   }  
   return list;  
}  
另外,common.h:
[cpp] view plaincopy
#ifndef CCSH_COMMON_H  
#define CCSH_COMMON_H  
  
#include   
#include   
#include   
  
#endif
error.h:
[cpp] view plaincopy
#ifndef CCSH_ERROR_H  
#define CCSH_ERROR_H  
  
typedef enum   
{  
   CC_OK,  
   CC_ERR_NOMEM  
}CC_ERR;
  
typedef struct   
{  
   CC_ERR  err_no;  
   char    *err_str;  
}cc_err_info;  
  
extern cc_err_info errs[];  
  
// global error number  
extern int         errno;  
  
void   cc_err(CC_ERR err_no);  
  
#endif
error.c:
[cpp] view plaincopy
#include   
#include "error.h"  
  
cc_err_info errs[] =   
{  
   {   CC_OK,              "no error"},  
   {   CC_ERR_NOMEM,       "no enough mem"}  
};  
  
int        errno;  
  
void   cc_err(CC_ERR err_no)  
{  
   printf("%s\n", errs[err_no].err_str);  
   errno = CC_ERR_NOMEM;  
}  
A文件中函数前面的cc是什么?
Q它是我的标志。
A那好吧。现在可以解决第二个问题了。
Q为了将不同的处理分离,下面首先把main函数的代码转移:
入口文件main.c:
[cpp] view plaincopy
#include   
#include "internal_main.h"  
  
int main(int argc, const char *argv[])  
{  
   return  cc_internal_main(argc,argv);  
}  
internal_main.h:
[cpp] view plaincopy
#ifndef CCSH_INTERNAL_MAIN_H  
#define CCSH_INTERNAL_MAIN_H  
  
int cc_internal_main(int argc, const char*argv[]);  
  
static int cc_process_string(char*str);  
  
#endif
interl_main.c:
[cpp] view plaincopy
#include   
#include "internal_main.h"  
#include   
#include "buf_to_arglist.h"  
#include "error.h"  
#include "str_process.h"  
  
  
int cc_internal_main(int argc, const char*argv[])  
{  
   char    buf[4096];  
   char    *temp_buf = (char *)buf;  
     
repeat:   
   cc_print_tipinfo();  
  
   memset(buf, 0, sizeof(buf));  
   temp_buf = fgets(buf, sizeof(buf)), stdin);  
  
   if(temp_buf == NULL)  
   {  
       goto repeat;  
   }  
   else  
   {  
       cc_process_string(buf);  
       goto repeat;  
   }  
     
   return 0;  
}  
  
static int cc_process_string(char*str)  
{  
   if(str[0] == '\n')  
       return 0;  
   str[strlen(str) - 1] = '\0';  
     
   cc_arg_list *list = cc_buf_to_arglist(str);  
   if(!list)  
   {  
       return errno;  
   }  
   // cc_arg_list_show_all_args(list);
   if(cc_str_is(list->head->str, "echo"))  
   {  
       cc_execute_echo(list, str);  
   }  
   cc_arg_list_free(list);  
     
   return 0;  
}  
第三个问题,显示提示符:
tip_info.h:
[cpp] view plaincopy
#ifndef CCSH_TIP_INFO_H  
#define CCSH_TIP_INFO_H  
  
void   cc_print_tipinfo();  
  
#endif
tip_info.c:
[cpp] view plaincopy
#include   
#include "tip_info.h"  
  
void   cc_print_tipinfo()  
{  
   printf("$");  
}  
echo.h:
[cpp] view plaincopy
#ifndef CCSH_ECHO_H  
#define CCSH_ECHO_H  
  
#include "arglist.h"  
  
int    cc_execute_echo(cc_arg_list   *arg_list, const char *buf);  
  
#endif
echo.c:
[cpp] view plaincopy
#include   
#include "echo.h"  
#include "str_process.h"  
#include   
  
int    cc_execute_echo(cc_arg_list   *arg_list, const char *buf)  
{  
   cc_arg_obj *arg = arg_list->head->next;  
   if(!arg)  
       return 0;  
   if(cc_str_begin_with(arg->str, '-'))
   {  
       size_t len = strlen(arg->str);
       if(len != 2)  
       {  
           printf("%s\n", arg->buf_pointer);  
           return 0;  
       }  
       else  
       {  
           if(arg->str[1] == 'n')  
           {  
                arg = arg->next;  
                printf("%s",arg->buf_pointer);  
                return 0;  
           }  
           else  
           {  
                printf("%s\n",arg->buf_pointer);  
                return 0;  
           }
       }  
   }  
   else  
   {  
       printf("%s\n", arg->buf_pointer);  
       return 0;  
   }  
     
   return 0;  
}  
A上面的代码是只处理了echo命令(它可以替代之前说的print命令),运行一下:
echo命令的-n参数表示不输出最后的换行。现在将之前的hello, verquit, exit命令都加上吧。
Q version.h:
[cpp] view plaincopy
#ifndef CCSH_VERSION_H  
#define CCSH_VERSION_H  
  
void   cc_show_version();  
  
#endif
version.c:
[cpp] view plaincopy
#include   
#include "version.h"  
  
void   cc_show_version()  
{  
   printf("ccteam shell 1.0\n");
}  
修改后的cc_process_string函数如下:
[cpp] view plaincopy
static int cc_process_string(char*str)  
{  
   if(str[0] == '\n')  
       return 0;  
   str[strlen(str) - 1] = '\0';  
     
   cc_arg_list *list = cc_buf_to_arglist(str);  
    if(!list)  
   {  
       return errno;  
   }  
   // cc_arg_list_show_all_args(list);
   if(cc_str_is(list->head->str, "echo"))  // like print command  
   {  
       cc_execute_echo(list, str);  
   }  
   else if(cc_str_is(list->head->str, "hello"))  
   {  
       printf("hello, i am a interpreter!\n");  
   }  
   else if(cc_str_is(list->head->str, "ver"))  
   {  
       cc_show_version();  
   }  
   else if(cc_str_is(list->head->str, "quit") ||cc_str_is(list->head->str, "exit"))  
   {  
       exit(0);  
   }  
   else  
   {  
       printf("no such command...\n");  
   }  
   cc_arg_list_free(list);  
     
   return 0;  
}  
上面对于不同输入的具体处理,有一些已经分离出去了。工程保存为ccsh,
运行结果:
A不过,对于一个类似bash或者python解释器,上面做的仅仅是很初步的功能,对于复杂语法解析还没有涉及;但可以肯定的是,如果继续去扩展,这只是时间的问题。
作者:陈曦
日期:2012-6-12   11:31:12
环境:[Mac10.7.1  Lion  Intel-based x64  gcc4.2.1  xcode4.2]
转载请注明出处


[ 本帖最后由 tiankai001 于 2012-12-15 08:00 编辑 ]
此帖出自单片机论坛
 
 
 

回复
您需要登录后才可以回帖 登录 | 注册

随便看看
查找数据手册?

EEWorld Datasheet 技术支持

相关文章 更多>>
关闭
站长推荐上一条 1/10 下一条

 
EEWorld订阅号

 
EEWorld服务号

 
汽车开发圈

About Us 关于我们 客户服务 联系方式 器件索引 网站地图 最新更新 手机版

站点相关: 国产芯 安防电子 汽车电子 手机便携 工业控制 家用电子 医疗电子 测试测量 网络通信 物联网

北京市海淀区中关村大街18号B座15层1530室 电话:(010)82350740 邮编:100190

电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2024 EEWORLD.com.cn, Inc. All rights reserved
快速回复 返回顶部 返回列表