肃然且通亮的雏菊2 哭后累了
关注数: 1 粉丝数: 261 发帖数: 3,202 关注贴吧数: 10
小话c语言(21) 环境:[Mac 10.7.1 Lion Intel-based x64 gcc4.2.1 xcode4.2] 转载请注明出处 Q: 对于编译过程的词法分析,到底应该使用什么方式? A: 可以肯定的是,必然要对需要处理的数据挨个字符判断,然后在恰当的位置截断,得到一个个的token. Q: 为什么得挨个字符都判断? A: 因为编码采用源代码的方式,你无法判断程序员下一个字符是什么。比如int i;和int1i; 这两种语句显然含有不同的符号。 Q: 如何进行词法分析? A: 一种很简单的思路就是,用一个状态保存在处理到各个字符时的状态,比如是标识符或者数字或者空格等等,直到状态改变到可以认定是不同token的时候结束。 Q: 给个设计图吧。 A: 现在不用看设计图,它来源于如下的实践。 [cpp] view plaincopy cur_state = STATE_UNKNOWN; state = STATE_START; if(isdigit(*buf++)) { state = STATE_NUM; continue; } if(isblank(*buf++) && state == STATE_NUM) { cur_state = STATE_NUM; state = STATE_BEGIN; continue; } 其实,这里的核心在于将不同符号对应的字符给区别开,在一个字符无法表达此符号时将它截断,token形成。 Q: 是否在token类型变多的情形下,上面的代码将变得很复杂? A: 是的。token类型很多,截断token的可能和条件将变多,必然要进行恰当处理才能正确截断token,这是个内部复杂但不难的过程,可能需要分离复杂到子模块中。 Q: 可以尝试写代码了吧。 A: 是的。如下是对上面描述的状态机的结构定义: [cpp] view plaincopy typedef struct { char *buf; char *begin; char *cur; char *end; Lex_state state; Lex_sub_state sub_state; }Token_state; 对于Lex_state和Lex_sub_state的定义如下: [cpp] view plaincopy typedef enum { Lex_state_begin, Lex_state_id, Lex_state_literal_num, Lex_state_literal_char, Lex_state_literal_str, Lex_state_op, Lex_state_end, Lex_state_err }Lex_state; typedef enum { Lex_sub_state_begin, Lex_sub_state_underline, Lex_sub_state_alpha, Lex_sub_state_dec_num, Lex_sub_state_oct_num, Lex_sub_state_hex_num, Lex_sub_state_op, Lex_sub_state_quot, Lex_sub_state_space, Lex_sub_state_semi, Lex_sub_state_literal_char_begin, Lex_sub_state_literal_char_end, Lex_sub_state_literal_str_begin, Lex_sub_state_literal_str_end, Lex_sub_state_end }Lex_sub_state; Q: 对于最终分析出来的token,用什么结构保存? A: 先做个简单的结构: [cpp] view plaincopy typedef struct { char *name; Token_type type; }Token; typedef struct { Token **p; int size; int capacity; }TokensTable; Token是单一的符号,TokensTable是数个Token的**。对于,Token_type定义如下:
小话c语言(19) 环境:[Ubuntu 11.04 Intel-based x64 gcc4.5.2 CodeBlocks10.05 AT&T汇编 Intel汇编] Q: 举个例子吧。 A: 下面的代码的目标是计算1+2的值,最后放到变量temp中,并输出: [cpp] view plaincopy #include <stdio.h> #include <string.h> #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 <printf@plt&gt; 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,它代表什么?
小话c语言(17) [Win7 vs2010] Q: C库和系统api之间是什么关系? A: 如下图,简单示意: 可以看出,C库一部分是使用系统api实现自身功能(比如文件操作),另一部分并不会直接依赖系统api,单独实现功能(比如字符串处理)。另外,对于驱动模块,按照不同的理解,也可以放入操作系统内部或者操作系统下层;如果把操作系统看成隐形的CPU和内存的驱动,那么它也可以看成和常规意义的硬件驱动是平级的。而,C库,从理论上来说,没必要和驱动有依赖关系。当然,访问操作系统的方式不仅仅是它提供的api,也是可以通过其它方式来访问。 Q:c库和多线程到底什么关系? 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; goto done; } 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的支持。
小话c语言(16) [Mac 10.7.1 Lion Intel-based x64 gcc4.2.1] Q: 有的时候,记得在某个目录下写过某个变量或者其它什么文本形式的东西,但是后来忘记写在哪个文件里了,怎么找到? A: 这个就需要用到grep命令了。它很强大,尤其对于开发或者寻找某个东西在哪里的时候。举个例子,有个目录里面有一些文件: 而且,有一些文件里面含有main字符串,现在需要把它们找出来: 很可惜,在mac10.7.1下面,grep的一个参数-r似乎无效,不能递归到子目录下搜索;不过在cygwin里面是ok的;bug列表中没有找到,但是实践确实无效,也许是个bug吧。上面测试用的grep版本是2.5.1. 当然,它还支持很多参数,比如-i忽略大小写,-E进行egrep扩展等等。 Q: 经常会遇到,创建一个进程,但是后来想把它关闭,ps -ax命令得到一堆进程,不便于寻找,怎么快速能定位它? A: 同样可以使用上面的grep命令。例如,需要寻找系统是否启动了httpd进程: Q: 有时,需要分析一个生成的可执行文件的内部结构,怎么办? A: 使用otool命令,它异常的强大。它支持很多参数,帮您将可执行文件,也包括中间文件、库等文件的内部展示地一览无余。 Q: 我需要查看一个可执行文件的mach头,怎么办? A: 可以使用-h参数。如下,写一个简单的代码,保存为hello.c: [cpp] view plaincopy #include <stdio.h> int main() { int i = 2; printf("%d\n", i); return 0; } 使用gcc -o hello hello.c编译得到hello.它将对hello进行分析: 用它和系统的mach header头文件结构对应,就可以得到它的具体含义。同理,使用-l命令可以得到mach-o格式文件的load commands. Q: 我想得到某个可执行文件依赖哪些动态库,怎么办? A: 可以使用otool命令的-L参数即可。同上使用上面的hello文件,
小话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 <stdio.h> 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_sy***ive_support EH_frame0: Lsection_eh_frame: Leh_frame_common:
小话c语言(14) [Mac 10.7.1 Lion Intel-based x64 gcc 4.2.1] Q: 如何让编译的文件可以被gdb调试? A: 可以加入-g参数。如下代码,保存为hello.c: [cpp] view plaincopy #include <stdio.h> 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 <stdio.h> 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等参数。
小话C语言(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是保存成功创建线程的ID; attr表示线程的相关属性,start_routine表示线程执行的函数, arg表示线程执行的使用的参数。示例代码(保存为testForC.c): [cpp] view plaincopy #include <stdio.h> #include <string.h> #include <unistd.h> #include <pthread.h> #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 <stdio.h> #include <string.h> #include <unistd.h> #include <pthread.h> #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; } 运行结果: 上面是在子线程输出字符串的过程中截取的,子线程会一直运行下去,知道自己的循环退出。
小话c语言(8) [Mac-10.7.1 Lion Intel-based gcc 4.2.1] Q: 可以把运算符看成特殊的标识符么? A: 是的。例如 >= 运算符两个字符之间不能含有空格,这和标识符是类似的。 [cpp] view plaincopy #include <stdio.h> int main() { 1 > = 2; return 0; } 编译: 可以看到,> 和 = 符号中间的空格导致了编译器不能理解。 Q: 为什么会产生运算符结合性这个概念? A: 结合性是在优先级相同的情况下才会进行结合性的判断得到表达式运算的真正的顺序。 Q: 赋值运算符怎么证明确实是右结合性? A: 如下代码: [cpp] view plaincopy #include <stdio.h> int main() { int a, b, c; a = b = c = 1; return 0; } gcc -S operator.c得到汇编代码(部分): [cpp] view plaincopy movl $1, -20(%rbp) movl -20(%rbp), %eax movl %eax, -16(%rbp) movl -16(%rbp), %eax movl %eax, -12(%rbp) -20(%rbp)即是表示c,-16(%rbp)表示b, -12(%rbp)表示a, 可以看到依次给c, b, a赋值,也就是体现了如下运算过程: (a = (b = (c = 1))); Q: 如下代码为什么结果始终不对? [cpp] view plaincopy #include <stdio.h> int main() { int a = 2; if(a & 1 == 0) printf("a & 1 == 0"); else printf("a & 1 != 0"); return 0; } 为什么一直输出“a & 1 != 0” ? A: 这是因为==的优先级高于表示位与运算符&.所以a & 1 == 0的实际代码是a & (1 == 0),也就是a & 0, 当然结果不是预期了。可以看下它的汇编(部分): [cpp] view plaincopy leaq L_.str(%rip), %rcx movq %rcx, %rdi callq _printf [cpp] view plaincopy L_.str: .asciz "a & 1 != 0" 可以看到编译器进行了优化,直接输出L_.str字符串,根本没有进行运行时再次运算if表达式的值。在这种情况下,需要注意是否忽略了运算符优先级导致编译器直接优化了。 Q: sizeof到底是个运算符还是关键字? A: 它应该被看成运算符。下面是c标准内容: [plain] view plaincopy 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可以看成运算符,而从词法的角度来说,它不能看成关键字。不过依然不能把它当成变量来申明: [cpp] view plaincopy #include <stdio.h> int main() { int sizeof = 1; return 0; } 编译: 可以看到编译出错了。 Q: 都说sizeof在编译期就可以计算出数值,怎么证明?
小话c语言(7) [Mac-10.7.1 Lion Intel-based, gcc 4.2.1] Q: 指针到底是什么? A: 有一天,你初到合肥,要到一个地方,但是路线不熟悉。遂问了一个路人,请问乌邦托在哪里?路人答曰:向南走即可。这个回答就像指针一样,告诉你一个方向。但是,到底向南多少,这就像是指针类型决定的大小。 Q: 看到那个const修饰指针的时候,老是搞不清楚到底是指针是常量还是指针指向的变量是常量? A: 其实很简单,采用从右向左的读法即可搞定这些。如下例子: [cpp] view plaincopy #include <stdio.h> int main (int argc, const char * argv[]) { int i = 100; const int *pi = &i; *pi = 200; return 0; } 保存为const.c, 使用gcc -o const const.c编译: 可以看到编译错误,表明pi是个不可更改其指向数据的指针。按照上面的从右读的原则即为: const int * pi 常量 整形 指向 pi 读为: pi指向整形常量,即pi指向什么变量可以改变,但是指向的变量的值不可改变。当然,使用类型方法,const int *pi和 int const *pi的含义是一致的。 如下代码就是ok的: [cpp] view plaincopy #include <stdio.h> int main() { int i = 100, j = 200; const int *pi = &i; pi = &j; return 0; } 编译ok. 另外一种情况: [cpp] view plaincopy #include <stdio.h> int main() { int i = 100, j = 200; int *const pi = &i; pi = &j; return 0; } 编译: 可以看到,编译错误表示pi是只读的,不可以更改pi的值。再使用从右向左的读法: int * const pi 整形 指向 常 pi 读为: pi常指向整形这也意味着,pi的值不能更改,但是没有意味着pi指向的数据不可以更改。 [cpp] view plaincopy #include <stdio.h> int main() { int i = 100, j = 200; int *const pi = &i; *pi = j; return 0; } 如上代码,编译ok. Q: 看过很多代码中含有函数指针,它的本质是什么? A: 它的本质即为一个指针,理论上函数的地址在编译期即可计算得到(当然在链接或者运行时还可能有重新定位)。先看一个简单的例子: [cpp] view plaincopy #include <stdio.h> #define PRINT_D(intValue) printf(#intValue" is %d\n", (intValue)); int add(int a, int b) { return a + b; } int main() { int (*func)(int, int) = add; PRINT_D(func(1, 2)) return 0; } 保存为func_ptr.c, 编译运行: 分析下代码: int (*func)(int, int)表示声明一个函数指针,它的参数是2个整形,返回值为1个整形;什么地方能体现出是函数指针呢?就在于func前面的*号。 = add; 表示此指针指向add函数。c语言的编译原则是函数编译之后可以确定当前编译状态的地址。为了确定,我们查看下汇编代码:
小话c语言(5) [Mac-10.7.1 Lion Intel-based] Q: 预处理到底干了什么事情? A: 预处理,顾名思义,预先做的处理。源代码中的头文件包含,宏以及条件编译的东西都会在预处理的时候搞定。换句话说,以#开头的语句即为预处理。但是,如果#被包含在引号里面,那就只是单纯的字符或者字符串了。 Q: 怎么证明预处理的存在? A: 如下代码,保存为macro.c: [cpp] view plaincopy #include #define NUM 100 int main() { printf("%d\n", NUM); return 0; } 使用gcc -E macro.c -o macro.i得到预处理的结果(因为篇幅问题,只截取最后数行代码):[cpp] view plaincopy # 500 "/usr/include/stdio.h" 2 3 4 # 2 "macro.c" 2 int main() { printf("%d\n", 100); return 0; } 可以看到,源代码中的NUM已经被替换成了宏定义的100. 事实上,预处理中的宏同样可以在命令行中指定,如下代码,保存为macro.c: [cpp] view plaincopy #include int main() { printf("%d\n", NUM); return 0; } 可以看到代码中的NUM没有被定义,然后使用编译命令加上对NUM的宏定义: gcc -DNUM=100 macro.c -o macro 编译结束,没有问题,运行亦ok. 同理,对于头文件包含以及条件编译都可以通过预处理命令得到处理之后的代码形式,这样会更好地理解预处理的含义。调试宏也不是一个简单的事情,如果很难确定某个宏到底有没有作用或者宏对应的字符串到底是什么的时候,使用预处理命令得到结果是很好的方式。 Q: 有时看到一个字符串前面带有一个#符号,它表示什么含义?形如: [cpp] view plaincopy #define PRINT_CH(ch) printf(#ch" is %c\n", (ch)); A:它表示将对应字符组合转换成相应的字符串格式。使用如下代码,保存为macro.c: [cpp] view plaincopy #include #define PRINT_CH(ch) printf(#ch" is %c\n", (ch)); int main() { PRINT_CH('a') return 0; } 使用gcc -E -o macro.i macro.c编译命令得到预处理后的文件macro.i. 使用如下命令得到预处理文件的最后10行: 可以看到PRINT_CH('a')宏被处理成: printf("'a'"" is %c\n", ('a')); #ch的作用就是将它变成"ch"的模样。两个字符串字面量放一起相当于字符串拼接的作用。编译和运行结果: Q: 除了上面的#符号,还会看到##符号,它又是什么含义? A: 它是代表参数连接,连接过程很单纯,让你看不到一点改变。举个例子: [cpp] view plaincopy #include #define CATENATE(a, b) a##b int main() { int ab = 1; printf("%d\n", CATENATE(a, b)); return 0; } 保存为preprocess.c,预处理后的结果是(仅截取最后数行代码): [cpp] view plaincopy # 2 "preprocess.c" 2 int main() { int ab = 1; printf("%d\n", ab); return 0; } 编译运行: 可以发现, a##b得到的结果就是ab这个符号。
小话C语言 前言----小话c语言 不知道该怎么开头,不过开头的几个字都写了,就继续写下去吧。 看过很多以大话开头的书籍,觉得也不怎么样,觉得还没达到大话的层次,本人本着谦虚的精神,暂且以小话开头吧;可能读者看完,觉得连小话都谈不上,先不管这些了;如果读者确实都觉得连小话都谈不上,到时候我再改个名字好了,这样至少也对得起文章的标题。 言归正传,回到主题吧。 以前写过关于c语言的学习资料,发现它真不是容易就能写好的,里面涉及到很多很多的东西;如果是以基础为目的的,那需要描述的就更多了;如果是稍微提高一些的,那么可能可以少写一些字。不过,以c语言的本质出发,实在太复杂了,如果让c语言之父来描述,估计那本书也没能足以表达他所有的精神和思想,毕竟核心思想还是在Dennis Ritchie心里,不过他已经离我们而去了,先默哀一下。 语言都是相通的 c语言,也是一种语言,和我们中国人平时说的中文其实是类似的,只不过一个最终是给机器来理解的,一个是让人来理解的。我不知道哪种应该更复杂,但是,有一点是可以肯定的,是语言它的语法就不会太复杂,否则不要说是笨笨的机器,就是地球上应该是最高级的人类可能都不能很好地理解,如果这样,这种语言的存在价值就需要思考了。"你好",这句话表示的就是个问候语,如果非要细节地分析内部的语法,可以理解成主谓结构吧,"你"是主语,"好"当成谓语吧。"int i = 1;" 理解成定义一个整形变量i; int表示变量类型, 后面跟着变量名,再跟着赋值号=,再跟着一个赋值的数据,最后以分号结束即可。这个结构可以用如下的表达式来表达: 类型 类型名 = 初值; 上面的看起来,不是很复杂,就像理解"你从哪里来?", "我从这里来。"这样的话一样。 为什么需要编译器 写完了int i = 1;之后,机器如何理解呢?当然,没有哪个机器能一下子理解这个。因为,有公司已经把机器的cpu设计成只能理解机器语言的了,那怎么办呢?只能用一种东西将上面的语句翻译成机器指令,传给机器的cpu,机器就能理解并执行了,而,这个东西就是编译器。不过,有人可能会说,我使用了bash终端,我输入 ls -l再回车,就可以帮我执行命令?哪里有编译器? 解释器是什么 上面说的那个过程真没用到编译器,而是解释器。其实解释器就做了编译器的事情,首先会解析输入的字符串ls -l, 就像编译器解析int i = 1;这个字符串一样,然后解析其中的语义,最后执行对应的操作。 解释器到底是什么 这个东西真的不用多想,它就是一个经过编译器编译ok的程序而已。 解释器运行程序会比对应编译器编译后的程序运行的慢 说的基本是对的,一般是这样,因为解释器多了一层解释的过程,然后才执行。 结束语 好了,关于解释器和编译器的比较就先到此为止,前言的内容我不想写一些废话,因为多打一句废话也是消耗能量的;
C 与 C++ 的区别 大致的说,可以分为以下几点: 1,全新的程序程序思维,C语言是面向过程的,而C++是面向对象的。 2,C语言有标准的函数库,它们松散的,只是把功能相同的函数放在一个头文件中;而C++对于大多数的函数都是有集成的很紧密,特别是C语言中没有的C++中的API是对Window系统的大多数API有机的组合,是一个集体。但你也可能单独调用API。 3,特别是C++中的图形处理,它和语言的图形有很大的区别。C语言中的图形处理函数基本上是不能用在中C++中的。C语言标准中不包括图形处理。 4,C和C++中都有结构的概念,但是在C语言中结构只有成员变量,而没成员方法,而在C++中结构中,它可以有自己的成员变量和成员函数。但是在C语言中结构的成员是公共的,什么想访问它的都可以访问;而在VC++中它没有加限定符的为私有的。 5,C语言可以写很多方面的程序,但是C++可以写得更多更好,C++可以写基于DOSr程序,写DLL,写控件,写系统。 6,C语言对程序的文件的组织是松散的,几乎是全要程序处理;而c++对文件的组织是以工程,各文件分类明确。 7,C++中的IDE很智能,和VB一样,有的功能可能比VB还强。 8,C++对可以自动生成你想要的程序结构使你可以省了很多时间。有很多可用的工具如加入MFC中的类的时候,加入变量的时候等等。 9,C++中的附加工具也有很多,可以进行系统的分析,可以查看API;可以查看控件。 10,调试功能强大,并且方法多样
提问的艺术 提问之前 在通过电邮、新闻组或者聊天室提出技术问题前,检查你有没有做到: 1. 通读手册,试着自己找答案。 2. 在FAQ里找答案(一份维护得好的FAQ可以包罗万象:)。 3. 在网上搜索(个人推荐google~~~)。 4. 向你身边精于此道的朋友打听。 当你提出问题的时候,首先要说明在此之前你干了些什么;这将有助于树立你的形象:你不是一个妄图不劳而获的乞讨者,不愿浪费别人的时间。如果提问者能从答案中学到东西,我们更乐于回答他的问题。 周全的思考,准备好你的问题,草率的发问只能得到草率的回答,或者根本得不到任何答案。越表现出在寻求帮助前为解决问题付出的努力,你越能得到实质性的帮助。 小心别问错了问题。如果你的问题基于错误的假设,普通黑客(J. Random Hacker)通常会用无意义的字面解释来答复你,心里想着“蠢问题…”,希望着你会从问题的回答(而非你想得到的答案)中汲取教训。 决不要自以为够资格得到答案,你没这种资格。毕竟你没有为这种服务支付任何报酬。你要自己去“挣”回一个答案,靠提出一个有内涵的,有趣的,有思维激励作用的问题–一个对社区的经验有潜在贡献的问题,而不仅仅是被动的从他人处索要知识–去挣到这个答案。 另一方面,表明你愿意在找答案的过程中做点什么,是一个非常好的开端。“谁能给点提示?”、“我这个例子里缺了什么?”以及“我应该检查什么地方?”比“请把确切的过程贴出来”更容易得到答复。因为你显得只要有人指点正确的方向,你就有完成它的能力和决心。 怎样提问 - 谨慎选择论坛 小心选择提问的场合。如果象下面描述的那样,你很可能被忽略掉或者被看作失败者: 1. 在风马牛不相及的论坛贴出你的问题 2. 在探讨高级技巧的论坛张贴非常初级的问题;反之亦然 3. 在太多的不同新闻组交叉张贴 - 用辞贴切,语法正确,拼写无误 我们从经验中发现,粗心的写作者通常也是马虎的思考者(我敢打包票)。 回答粗心大意者的问题很不值得,我们宁愿把时间耗在别处。 正确的拼写,标点符号和大小写很重要。 更一般的说,如果你的提问写得象个半文盲,你很有可能被忽视。 如果你在使用非母语的论坛提问,你可以犯点拼写和语法上的小错–但决不能在思考上马虎(没错,我们能弄清两者的分别)。 - 使用含义丰富,描述准确的标题 在邮件列表或者新闻组中,大约50字以内的主题标题是抓住资深专家注意力的黄金时机。别用喋喋不休的“帮帮忙”(更别说“救命啊!!!!!”这样让人反感的话)来浪费这个机会。不要妄想用你的痛苦程度来打动我们, 别用空格代替问题的描述,哪怕是极其简短的描述。 蠢问题: 救命啊!我的膝上机不能正常显示了! 聪明问题: XFree86 4.1下鼠标光标变形,Fooware MV1005的显示芯片。 如果你在回复中提出问题,记得要修改内容标题,表明里面有一个问题。一个看起来象“Re:测试”或者“Re:新bug”的问题很难引起足够重视。另外,引用并删减前文的内容,给新来的读者留下线索。 - 精确描述,信息量大 1. 谨慎明确的描述症状。 2. 提供问题发生的环境(机器配置、操作系统、应用程序以及别的什么)。 3. 说明你在提问前是怎样去研究和理解这个问题的。 4. 说明你在提问前采取了什么步骤去解决它。 5. 罗列最近做过什么可能有影响的硬件、软件变更。 尽量想象一个黑客会怎样反问你,在提问的时候预先给他答案。 Simon Tatham写过一篇名为《如何有效的报告Bug》的出色短文。强力推荐你也读一读。 - 话不在多 你需要提供精确有效的信息。这并不是要求你简单的把成吨的出错代码或者数据完全转储摘录到你的提问中。如果你有庞大而复杂的测试条件,尽量把它剪裁得越小越好。
1 下一页