标签 C 下的文章
1、const char *ptrconst char *ptr 表示的是指向常量的指针,不能通过该指针去修改指针所指向地址中的内容,但是可以修改该指针的指向。 可以通过其他普通指针对同样地址中的数据进行修改,测试例子如下:编译测试程序:gcc test.c -o test.exegcc编译报错,提示对指向常量的指针的错误操作。屏蔽23行的代码 ptr[0] = 'A';,重新编译,运行结果如下:2、char const *ptrchar const *ptr 和 const char *ptr 等价。3、char * const ptrchar * const ptr 表示的是指针的指向不可以被修改,但是可以修改指针指向地址中的内容。测试例子如下:编译测试程序:gcc test.c -o test.exegcc编译报错,提示指针的指向不可以被修改。屏蔽27行的代码 ptr = name;,重新编译,运行结果如下:4、总结const char *ptr 表示的是指向常量的指针,不能通过该指针去修改指针所指向地址中的内容,但是可以修改该指针的指向。char const *ptr 和 const char *ptr 等价。char * const ptr 表示的是指针的指向不可以被修改,但是可以修改指针指向地址中的内容。const放在*号前表示修饰的是指针指向的对象,const放在*号后表示修饰的是指针本身。根据上面章节的内容,下面的写法是什么意思应该比较简单吧:const char * const ptr;表示的是ptr指针的指向不可以被修改,ptr指针所指向地址中的内容不可以被修改。
在程序的开发和调试过程中,可以使用一个宏定义来代替printf输出调试信息,等程序开发完成之后,如果不需要调试信息,直接将宏定义为空就行,这样便于随时打开和关闭调试信息。这样的调试程序的宏一般是可变参数宏,因为打印函数printf是可变参数的,因此定义的宏也要求支持可变参数。可变参数宏可以接受可变数目的参数,就像可变参数的函数一样。可变参数宏也使用三个点(...)来表示宏的可变参数性。__VA_ARGS__ 宏是用来表示可变参数宏的可变参数的内容。简单的说就是将可变参数宏中的 ... 的内容原样传递给右边 __VA_ARGS__ 所在的位置。示例代码如下:通过屏蔽或打开程序中的 __DEBUG__ 宏,可以让调试信息输出或者不输出。
在编写C程序时,如果想要打印某个字符串,而字符串的内容比较多,这就涉及到对这个长字符串进行书写换行,这里的换行并不会对最终的显示结果进行换行,只是为了阅读代码能够更加的清晰,不至于字符串的内容过长影响代码的阅读体验。1.长字符串示例上述代码需要打印的字符串内容比较长,在代码阅读软件中,可能需要拖动水平方向上的滚动条,才能看清楚字符串的完整内容,这极大的影响了代码的阅读效率。下图是上述代码的运行结果:2.书写长字符串的换行方法方法一:利用双引号对长字符串进行换行在对长字符串进行书写换行时,可以使用双引号将长字符串拆分成多个子字符串,编译器在编译处理时会自动的拼接这些子字符串,不会影响最终想要的显示效果,示例代码如下:上述示例的运行结果如下:运行结果和长字符串未拆分时的效果一样。方法二:利用反斜杠对长字符串进行换行可以使用反斜杠对长字符串进行拆分,反斜杠后的换行符会被C忽略,所以可以拆分字符串,但是下一行的空格会被计算在内,这反斜杠拆分字符串的一个问题吧,示例代码如下:上述代码的运行结果如下图所示:运行结果和最终想要的结果还是有差异的,插入了一些不需要的空格,因为反斜杠拆分的字符串会把下一行的空格也计算在内。3.总结对长字符串的书写换行,建议使用双引号进行拆分,这是最完美的,显示效果和最终想要的效果是一致的。
1.终端中如何输入EOF在C语言运行过程中,有时需要在终端手动输入EOF,其输入方法与操作系统有关。windows系统:实际上输入是在dos下面执行的,也就是运行时的那个黑底窗口。在windows下输入EOF需要输入CTRL+Z。Linux/Unix/Mac操作系统:在这类操作系统下,要输入EOF需要输入CTRL+D。2.EOF输入时机不同,结果可能不同终端输入EOF,EOF作为文件结束符时的情况:EOF虽然是文件结束符,但并不是在任何情况下输入Ctrl+D(Windows下Ctrl+Z)都能够实现文件结束的功能,只有在下列的条件下,才作为文件结束符。(1).遇到getchar()(或者getc())函数执行时,要输入第一个字符时就直接输入Ctrl+D,就可以跳出getchar()(或者getc()),去执行程序的其他部分;(2).在前面输入的字符为换行符时,接着输入Ctrl+D;(3).在前面有字符输入且不为换行符时,要连着输入两次Ctrl+D,这时第二次输入的Ctrl+D起到文件结束符的功能,至于第一次的Ctrl+D的作用并不是文件结束符,而只是相当于换行符的功能,即结束当前的输入,引发getchar()(或者getc())提示下一轮的输入。3.总结EOF的作用也可以总结为:当终端有字符输入时,Ctrl+D产生的EOF相当于结束本行的输入,将引起getchar()(或者getc())新一轮的输入;当终端没有字符输入或者可以说当getchar()(或者getc())读取新的一次输入时,输入Ctrl+D,此时产生的EOF相当于文件结束符,程序将结束getchar()的执行。
1.基本概念C语言中的inline关键字是C99标准的关键字,它的作用是将函数展开,把函数的代码复制到每一个调用该函数的地方。这样调用该函数的地方就可以直接执行函数代码,而不发生跳转、压栈等一般性函数操作。可以节省时间,也会提高程序的执行速度。使用inline关键字修饰的函数就是内联函数。在C语言中,如果一些函数被频繁的调用,尤其是这些函数在递归函数中被调用,那么如果递归过程很长,就有可能出现递归还没完成,但是栈空间被用完了。此时如果将这些函数定义为内联函数,那么在递归函数中,内联函数是不会发生函数跳转和压栈操作的,因此也就不存在栈溢出的情况了。2.内联函数的定义方法将一个函数定义为内联函数是比较简单的,直接在定义函数的时候,在函数的前面添加inline关键字即可。内联函数(inline函数)一般和static一起使用,如果内联函数定义在.c文件中定义并且和static关键字一起使用,那么这个内联函数的作用域就是当前的.c文件,其他.c文件不能使用该内联函数;如果内联函数在.h文件中定义并且和static关键字一起使用,那么这个内联函数能够被包含该.h文件的所有源文件或者头文件使用。内联函数的定义方法如下:static inline add(int a, int b){ return (a + b);}3.内联函数使用场景如果函数本身比较简洁,并且函数使用的频率比较高,那么就可以将函数定义为内联函数。当然了,如果函数本身比较简洁,但是使用的频率不高,也可以将函数定义为内联函数。在嵌入式开发中,内联函数还是比较重要的,将一些功能简洁的函数定义为内联函数,可以减少代码的跳转和栈空间的使用,RAM在嵌入式系统中还是比较宝贵的。使用内联函数可以使程序的执行效率更高,同时是代码更紧凑。当然了使用内联函数的缺点也比较明显,就是程序的大小会变大(程序占用flash的空间变大,占用的RAM不变),因为每一个调用内联函数的地方,都会有一个内联函数的拷贝。4.内联函数和宏定义的一些对比内联函数在某种程度上和宏定义很想,比如上述的内联函数可以使用宏定义来描述:#define add(a, b) ((a) + (b))虽然内联函数和宏很像,但是在使用方法上和宏还是有很大区别的,下面就从几个方面来对内联函数和宏进行对比:(1).展开的时机:内联函数在编译的时候展开,因此inline关键字是一个编译关键字。宏是在预处理时展开,因此#define关键字是一个预处理关键字。(2).参数类型检查:内联函数会进行严格的参数类型检查。宏不会检查参数类型,只是做简单的字符串替换,因此在使用带参数的宏时会有一些副作用,编写程序时要人为预防。(3).是否允许有复杂语句内联函数不允许出现复杂语句,如果出现复杂语句,该函数将不会展开,例如递归,大型循环等。宏定义对此不做要求。宏只是做字符串替换操作,而不了解语句的含义。(4).是否一定被展开内联函数不一定会被展开,是否展开是由编译器决定的。宏一定会被展开,只要使用了宏就可以保证被展开。
这篇文章摘抄自: <<Linux设备驱动开发详解(第二版)>>,在这里记录下来方便自己和其他小伙伴查阅。在 Linux 内核中,经常会看到do{} while(0)这样的语句,许多人开始都会疑惑,认为do{} while(0)毫无意义,因为它只会执行一次,加不加do{} while(0)效果是完全一样的,其实 do {}while(0)的用法主要用于宏定义中。这里用一个简单点的宏来演示:#define SAFE_FREE(p) do{ free(p); p = NULL;} while(0)假设这里去掉 do...while(0),即定义 SAFE_DELETE 为:#define SAFE_FREE(p) free(p); p = NULL;那么以下代码if(NULL != p) SAFE_DELETE(p)else .../* do something */会被展开为:if(NULL != p) free(p); p = NULL;else .../* do something */展开的代码中存在两个问题。(1)因为 if 分支后有两个语句,导致 else 分支没有对应的 if,编译失败。(2)假设没有 else 分支,则 SAFE_FREE 中的第二个语句无论 if 测试是否通过,都会执行。的确,将 SAFE_FREE 的定义加上{}就可以解决上述问题了,即:#define SAFE_FREE(p) { free(p); p = NULL;}这样,代码:if(NULL != p) SAFE_DELETE(p)else ... /* do something */会被展开为:if(NULL != p) { free(p); p = NULL; }else ... /* do something */但是,在 C 程序中,每个语句后面加分号是一种约定俗成的习惯,那么,如下代码:if(NULL != p) SAFE_DELETE(p);else ... /* do something */将被扩展为:if(NULL != p) { free(p); p = NULL; };else ... /* do something */这样,else 分支就又没有对应的 if 了,编译将无法通过。假设用了 do {} while(0),情况就不一样了,同样的代码会被展开为:if(NULL != p) do{ free(p); p = NULL;} while(0);else ... /* do something */不会再出现编译问题。do while(0)的使用完全是为了保证宏定义的使用者能在不出现编译错误的情况下使用宏,它不对其使用者做任何假设。
宏定义尤其是带参数的宏定义,特别容易出现一些隐藏问题,因为宏定义在预处理阶段是按照定义原封不动的进行展开,此时如果展开之后涉及到运算符优先级的问题,那么隐藏bug就此出现。这里我先列举一个简单的例子,然后归纳下带参数宏定义对于括号使用的一些说明。1.构造带有隐藏bug的宏定义下面定义两个带参数宏,MUL_TWO是将两个数进行相乘,MUL_THREE是将三个数进行相乘。#define MUL_TWO(val1, val2) (val1 * val2)#define MUL_THREE(x, y, z) (MUL_TWO(x, y) * z)比如我这里计算2 * 3 * 4的运算结果,那么只需调用宏MUL_THREE(2, 3, 4)就可得到计算结果为:24,计算结果是正确的。但是如果将MUL_THREE(2, 3, 4)修改为MUL_THREE(1+1, 1+2, 1+3),此时的运算结果又是多少呢,很简单,我们将这个宏进行展开,展开的过程如下所示:MUL_THREE(1+1, 1+2, 1+3) => (MUL_TWO(1+1, 1+2) * 1+3)(MUL_TWO(1+1, 1+2) * 1+3) => ((1+1 * 1+2) * 1+3)然后我们计算下,得出结果是7,是不是计算错误了。2.改造上述宏定义这里的宏定义还是比较简单的,并且大多数的小伙伴应该都知道在定义带参数的宏时,参数需要使用括号括起来,那么我们改造下上述的宏,改造结果如下所示:#define MUL_TWO(val1, val2) ((val1) * (val2))#define MUL_THREE(x, y, z) (MUL_TWO(x, y) * z)此时再来对MUL_THREE(1+1, 1+2, 1+3)进行展开,展开的过程如下所示:MUL_THREE(1+1, 1+2, 1+3) => (MUL_TWO(1+1, 1+2) * 1+3)(MUL_TWO(1+1, 1+2) * 1+3) => (((1+1) * (1+2)) * 1+3)然后我们计算下,得出结果是9,计算结果还是有问题。仔细检查下宏定义,原来是对MUL_THREE宏的z没有用括号括起来,这个问题也是比较容易犯的,修改好之后的宏如下所示:#define MUL_TWO(val1, val2) ((val1) * (val2))#define MUL_THREE(x, y, z) (MUL_TWO(x, y) * (z))此时再来对MUL_THREE(1+1, 1+2, 1+3)进行展开,展开的过程如下所示:MUL_THREE(1+1, 1+2, 1+3) => (MUL_TWO(1+1, 1+2) * (1+3))(MUL_TWO(1+1, 1+2) * (1+3)) => (((1+1) * (1+2)) * (1+3))此时的计算结果就是没问题的了。这里我再提个问题,为什么你在MUL_THREE宏中,只使用括号括起了z,为啥x和y你不同等对待,确实哈,如果对于不是很熟悉的小伙伴,可能看到我说的情况,会毫不犹豫的也对x和y进行同样的保护;也有的小伙伴看到我说的这个情况可能脑子里面就晕了。3.带参数宏定义对于括号使用的一些说明其实不对x和y做保护是有一个前提的,那就是你所定义的每一个宏定义都要确保对在当前宏中使用到的参数用括号进行保护。不知道各位明白我的意思不,不明白的话,看看我下面的总结吧。带参数宏定义,对于括号何时使用的总结:(1).带参数宏定义,如果参数在当前的宏中有进行运算,那么必须对该参数使用括号括起来(类似例子中MUL_THREE里面的z,MUL_TWO里面的val1和val2);(2).带参数宏定义,如果参数没有在当前的宏中有进行运算,而是直接当成参数传递给其他的宏,那么该参数是不用使用括号进行保护的(类似例子中MUL_THREE里面的x和y)。对于上面的总结第(2)点,能够对传递给其他宏的参数不进行括号保护是因为总结的第(1)点已经对宏做了一个规定,只要所有的宏定义都按照第(1)点进行书写,那么第(2)点自然也就不会出什么问题。各位小伙伴在定义带参数的宏时,按照我上面说的2点进行书写,什么隐藏bug的也就不会存在了。如果文中有什么问题欢迎指正,毕竟博主的水平有限。
- 1
本站信息
目前本站共被浏览 162783 次
目前本站已经运行 3508 天
目前本站共有 165 篇文章
目前本站共有 6 条评论信息
目前本站共有 104 个标签
目前本站共有 0 条留言信息
网站创建时间: 2015年03月01日
最近更新时间: 2023年11月26日
目前本站已经运行 3508 天
目前本站共有 165 篇文章
目前本站共有 6 条评论信息
目前本站共有 104 个标签
目前本站共有 0 条留言信息
网站创建时间: 2015年03月01日
最近更新时间: 2023年11月26日
JLink V9掉固件修复(灯不亮) 3Zephyr笔记2 - 在STM32F429上运行HelloWorld 2计算NandFlash要传入的行地址和列地址 1Linux MMC子系统 - 6.eMMC 5.1工作模式-设备识别模式 0Linux MMC子系统 - 5.eMMC 5.1工作模式-引导模式 0Linux MMC子系统 - 4.eMMC 5.1常用命令说明(2) 0
最新评论
标签云
Linux嵌入式实用技巧ARM内核学习问题集合CC++编程语言阅读笔记汇编Linux内核完全注释Windows驱动开发计算机基础ARM11ARMv7-ASTM32IDESublimeLinux内核学习eMMCMMC子系统Ubuntu操作系统OfficeVMWareAPUEgccRTOS中断漫游世界随笔感悟开发工具软件应用编程VsCodearmccarmclang编译器ZephyrSPIJLink网卡驱动安装各种芯片库函数NFSμCOS内核sambaFlashUnix命令与脚本输入法Linux内核设计与实现gitRIFFWAVJATGFTPar8161安装centos有线上网μCGUI字库工程建立右键菜单网络文件系统Firefox百度NTFS文件系统CodeBlocksCentOS数据结构算法PhotoShop51KeilQTUltraEditscanfglibc宏定义UIDGID优先级娱乐天地SourceInsight磁盘扇区总线I2CPDFBComparePythonI2SFPUMakefileSWDCPUARP软件推荐FileZilla