标签 编程语言 下的文章
广告位B招租:1
 2021-10-29T19:04:46.092336    |      C/C++    |     AilsonJack    |     暂无评论    |     72 views
在程序的开发和调试过程中,可以使用一个宏定义来代替printf输出调试信息,等程序开发完成之后,如果不需要调试信息,直接将宏定义为空就行,这样便于随时打开和关闭调试信息。这样的调试程序的宏一般是可变参数宏,因为打印函数printf是可变参数的,因此定义的宏也要求支持可变参数。可变参数宏可以接受可变数目的参数,就像可变参数的函数一样。可变参数宏也使用三个点(...)来表示宏的可变参数性。__VA_ARGS__ 宏是用来表示可变参数宏的可变参数的内容。简单的说就是将可变参数宏中的 ... 的内容原样传递给右边 __VA_ARGS__ 所在的位置。示例代码如下:通过屏蔽或打开程序中的 __DEBUG__ 宏,可以让调试信息输出或者不输出。
CC++编程语言计算机基础 阅读全文»
 2021-09-26T21:00:10.059909    |      C/C++    |     AilsonJack    |     暂无评论    |     122 views
在编写C程序时,如果想要打印某个字符串,而字符串的内容比较多,这就涉及到对这个长字符串进行书写换行,这里的换行并不会对最终的显示结果进行换行,只是为了阅读代码能够更加的清晰,不至于字符串的内容过长影响代码的阅读体验。1.长字符串示例上述代码需要打印的字符串内容比较长,在代码阅读软件中,可能需要拖动水平方向上的滚动条,才能看清楚字符串的完整内容,这极大的影响了代码的阅读效率。下图是上述代码的运行结果:2.书写长字符串的换行方法方法一:利用双引号对长字符串进行换行在对长字符串进行书写换行时,可以使用双引号将长字符串拆分成多个子字符串,编译器在编译处理时会自动的拼接这些子字符串,不会影响最终想要的显示效果,示例代码如下:上述示例的运行结果如下:运行结果和长字符串未拆分时的效果一样。方法二:利用反斜杠对长字符串进行换行可以使用反斜杠对长字符串进行拆分,反斜杠后的换行符会被C忽略,所以可以拆分字符串,但是下一行的空格会被计算在内,这反斜杠拆分字符串的一个问题吧,示例代码如下:上述代码的运行结果如下图所示:运行结果和最终想要的结果还是有差异的,插入了一些不需要的空格,因为反斜杠拆分的字符串会把下一行的空格也计算在内。3.总结对长字符串的书写换行,建议使用双引号进行拆分,这是最完美的,显示效果和最终想要的效果是一致的。
CC++编程语言计算机基础 阅读全文»
 2021-03-14T11:58:46.768758    |      C/C++    |     AilsonJack    |     暂无评论    |     302 views
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).是否一定被展开内联函数不一定会被展开,是否展开是由编译器决定的。宏一定会被展开,只要使用了宏就可以保证被展开。
CC++编程语言计算机基础 阅读全文»
广告位B招租:2
 2021-03-07T12:00:41.834717    |      C/C++    |     AilsonJack    |     暂无评论    |     251 views
这篇文章摘抄自: <<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)的使用完全是为了保证宏定义的使用者能在不出现编译错误的情况下使用宏,它不对其使用者做任何假设。
CC++编程语言计算机基础 阅读全文»
 2021-02-26T20:14:58.003964    |      C/C++    |     AilsonJack    |     暂无评论    |     116 views
宏定义尤其是带参数的宏定义,特别容易出现一些隐藏问题,因为宏定义在预处理阶段是按照定义原封不动的进行展开,此时如果展开之后涉及到运算符优先级的问题,那么隐藏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的也就不会存在了。如果文中有什么问题欢迎指正,毕竟博主的水平有限。
CC++编程语言计算机基础 阅读全文»
 2020-12-06T22:02:22.853039    |      C/C++    |     AilsonJack    |     暂无评论    |     734 views
通常情况下,像'>','<'和'=='这类的比较运算符的优先级要低于计算运算符。所以下面的两段代码是一样的:代码1:代码2:但是,如果有&和|参与的运算就要注意了,下面的代码3写法未必是你想的那样,其实际运算顺序是代码4那样的。代码3:代码4:是不是有点奇怪呀,这是因为&和|这两个运算符的优先级低于'>','<'和'=='这类的比较运算符的优先级。注:实际编程过程中,如果实在不清楚运算符的具体优先级关系,那么最好的保障就是在合适的地方添加上括号。比如代码3,我们应该加上括号,也就是代码5那样,才是我们想要的运算顺序:代码5:
CC++编程语言计算机基础 阅读全文»
广告位B招租:3
 2019-01-24T19:38:18.749000    |      C/C++    |     AilsonJack    |     暂无评论    |     1263 views
最近在写一个操作文件的函数,使用fseek()函数定位到文件某个位置,然后用fwrite()重新更新该位置的内容,发现该位置处的内容并没有更新,反而更新的内容竟然出现在了文件末尾,真的是不知道什么情况。下面先简单复现我所遇到的问题吧,示例代码如下:将上述代码编译,然后在编译输出文件所在的文件夹中创建一个test.txt文件,内容为:123456789abcdef:接着运行程序,打开test.txt,可以看到内容(字符W)被写到文件的末尾了:这是什么情况,明明使用fseek定位到文件的开头了,但是实际却写入到了文件末尾,好吧,当时我也是挺困惑的,难道是写fseek()接口函数的哥们还遗留有什么bug。后来上网查证,发现这是我们打开文件的所使用的模式(”a+”)在作怪。下面看看对该模式的描述,直接在终端输入:man fopen其中对fopen()函数涉及的a与a+模式的描述如下:a:Open for appending (writing at end of file). The file is created if it does not exist. The stream is positioned at the end of the file.a+:Open for reading and appending (writing at end of file). The file is created if it does not exist.  The initial file position for reading is at the beginning of the file, but output is always appended to the end of the file.上述对a与a+模式的描述大致内容是:打开一个文件,如果该文件不存在将创建文件,初始化的文件读指针位于文件的开头;对于文件的写操作,则始终将写入内容追加到文件的末尾,与文件指针没有关系。想必看到这里,大家也明白了。如果想使用fseek()函数定位写操作指针,那么就修改fopen()涉及的模式,这里修改为”r+”,就能实现将内容写到文件的开始了,对于自己的程序大家还是根据实际情况修改为相应的模式。如果有什么疑问,欢迎留言交流^_^。
编程语言CC++ 阅读全文»
 2018-08-31T22:56:41.226000    |      汇编    |     AilsonJack    |     暂无评论    |     936 views
ARM的数据处理指令大致可以分为3类:数据传送指令,运算指令,比较指令。数据处理指令只能对寄存器的内容进行操作,而不能对内存中的数据进行操作。所有ARM数据传送或算术逻辑运算指令均可选择使用S后缀,以使指令影响CPSR中的标志。1、ARM指令集-基本指令格式ARM是三地址指令格式,指令的基本格式如下:<opcode>  {<cond>}  {S}  <Rd> ,<Rn>{,<operand2>}其中<>号内的项是必须的,{} 号内的项是可选的。各项的说明如下:opcode:指令助记符;cond:执行条件;S:是否影响CPSR寄存器的值;Rd:目标寄存器;Rn:第1个操作数的寄存器;operand2:第2个操作数;灵活的使用第2个操作数“operand2”能够提高代码效率。它有如下的形式:1)、#immed_8r——常数表达式:如:ADD R0, R1, #3 ;R0 = R1 + 32)、Rm——寄存器方式:如:SUB R0, R1, R2 ;R0 = R1 – R23)、Rm,shift——寄存器移位方式:如:ADD R0, R1, R2, LSL, #3 ;R0 = R1 + R2<<32、数据传送指令ARM的数据传送指令有两条,如下所示:注:当后缀有S时,这些指令根据结果更新标志N和Z,在计算Operand2时更新标志C,不影响标志V。3、运算指令1)、算术运算指令注:当后缀有S时,这些指令根据结果更新标志N、Z、C、V。2)、逻辑运算指令注:当后缀有S时,这些指令根据结果更新标志N和Z,在计算Operand2时更新标志C,不影响标志V。4、比较指令注:这些指令直接影响N,Z,C和V标志位(不需要加后缀S)。MOV R0, #3CMN R0, #-2 ;Z=0CMN R0, #-3 ;Z=1CMN可以理解为,判断两个数是否互为相反数。
ARM编程语言汇编 阅读全文»
 2018-08-29T16:27:55.140000    |      汇编    |     AilsonJack    |     暂无评论    |     3507 views
最近在阅读《Linux内核完全注释》这本书,试图多了解Linux内核的运行过程。书中当然也涉及到汇编代码的编写。这本书的阅读笔记,应该会在后面陆续的发布,敬请期待吧。在Linux系统中,使用AT&T语法编写汇编代码时,遇到了一个问题,就是编译代码时会出现问题:"invalid instruction suffix for push"。我的编译环境:Ubuntu 14.04 x86_64,在终端中执行命令:as -o callee.o callee.s,此时就出现了错误提示:invalid instruction suffix for push,截图如下:错误原因是,在64位系统和32位系统的as命令对于某些汇编指令的处理支持不一样造成的。在文件callee.s中,包含指令:pushl  %ebp,该指令在64位系统下就编译不过。解决方法:在callee.s中,在代码头部添加.code32即可:然后再次编译该汇编文件,编译顺利通过。如果还有其他的问题,欢迎留言讨论。
编程语言汇编Linux内核 阅读全文»
广告位B招租:4
广告位B招租:2
广告位B招租:3
广告位B招租:4
  • 1

  本站信息

目前本站共被浏览 134680 次
目前本站已经运行 2834 天
目前本站共有 149 篇文章
目前本站共有 3 条评论信息
目前本站共有 100 个标签
目前本站共有 0 条留言信息
网站创建时间: 2015年03月01日
最近更新时间: 2022年11月26日
广告位E招租
Copyright © 2015~2021  说好一起走   保留所有权利   |  百度统计  蜀ICP备15004292号