你的浏览器版本过低,可能导致网站不能正常访问!
为了你能正常使用网站功能,请使用这些浏览器。

"最常见"三个预编译

[复制链接]
gaosmile 发布时间:2020-10-28 19:20
1、情景再现

  s5 q7 a' R+ Z

4 f) I1 a" o+ W
咬金,C语言学得怎么样了?
我都敲了2年C代码了,还有我不知道的吗?

* A, M6 ~5 [. [% s! S' _
4 L" q6 g( q: Y$ b
你狂,你继续狂!那我问你#error,#warning,#def这三个预编译干啥的?
+ y+ {* [$ m, M9 f! `
额~~~,这就尴尬了,平时看代码基本上都见过,没怎么研究过哦。
# j. E5 K7 D, V/ _
( W0 M! o2 h* l9 H+ F- ?
& F; J4 q4 R+ H& V
哈哈~,要不要我教教你呀?
小鲁班,跟大哥说说呗!
+ P8 {7 F* W5 I( O) Y: w
4 k% v) H/ g9 K& H# h
4 x7 o. w+ }1 W4 s
行吧,待会说我这队友啥也不懂!# L& `, E1 D3 O8 D; W; |
1
#error 与#warning

9 n$ H. Y4 ~$ Q8 r
    谈到预编译大家常用的有#if、#else、#ifdef、#ifndef、#endif等等条件编译选项。$ D1 N6 V, \; f1 t/ H5 ~
   
    然而在我们阅读一些大型的代码或者库的时候,一般都会看到有#error和#warning,可能有些小伙伴一扫而过并没有了解清楚这些预编译指令到底该怎么用,写了很久的代码估计也重来没有敲过他们。
: m% W' t. k5 a; q  O5 M9 ?
  • #error / #warning
    ' y; _7 p" f' z: {
  • 形式 : #error / #warning message
  • 作用 : 生成一个编译错误事件并停止编译/发出警告信息
  • 注意 : message 可以不需要双引号。
    : z) |" _* L7 \) t( l' [6 l

    ( |( I* m0 u8 Y9 O

( B9 W1 Z) S" w. m: t
参考demo:
#include <stdio.h>
: K# @. t& v1 B. k+ w* P7 W#include <stdlib.h>$ j/ C9 z0 ?! O: X* j9 h

& B5 L# f% O* B/ ^8 l" r//#define configUART_N 5
1 r, l) A8 @% g# p9 }$ t4 W5 o2 \! C& s, C" |  ^
#ifndef configUART_N
: ~* y, a' a0 T* y0 ^1 O    #error configUART_N must define+ Z' z3 W0 b* `+ M5 n7 e) [& D
// #error "configUART must define"/ p' m" ?" |( h
// #warning "configUART must define"
+ g/ w$ S( p" Q/ p0 `3 X#endif+ u+ ^5 s# o" ~/ ?* b
  }; M7 `! I% E" w) I+ ]6 \6 U" J, P7 m
#if configUART_N > 41 j- ^* M$ r9 N  Y
   #error configUART_N must not be less than 4
% E0 ^3 H: `" f; p+ W3 |// #error "configUART_N must not be less than 4"4 \- J1 ?5 S0 ^) v, L
// #warning "configUART_N must not be less than 4"' o/ K  |; `( G* D2 I) D
#endif
1 F& k% M( w: }. g  |- j& C
2 h6 I/ o- g9 }" U; {7 S/ r  z5 E/***************************************+ n. i$ R: t0 H* h: \5 h
* Fuction: 进行预编译测试 0 h1 P3 D) ^$ Y/ A
* Author 最后一个bug)
3 `1 m+ H" C/ f. M7 c **************************************/2 s3 K- h& j8 w: z' @% E
int main(int argc, char *argv[]) {
" k, s) s- U& {) a" U- w2 q; ^    printf("公众号;最后一个bug\n");7 }( ~4 @3 \& D/ y" c( O6 s
    return 0;
( d/ L* b2 X. e}
# ]0 Z0 _) p: M+ w; }# y
输出结果:
! m+ m* R/ V$ L/ L- C3 |
微信图片_20201028191356.png
  • 编译失败,无法生成可执行文件
    3 P' c, y) X9 L  D! C1 Q. s

. M3 y' K/ ~( X0 B- f
微信图片_20201028191400.png
  • 上面是放开宏,且使用warning的情况,无其他错误的情况下可以生成可执行文件。4 K' N4 z3 _  }* G3 w; n
    : k4 n( s  v/ Z9 Z4 h2 p( r; T

& d, K& M" l5 v$ ^- k) e7 H8 k
解释一下:
  • 通过上面的测试代码可以了解到,通过配合条件预编译#if等,#error和#warning能够在编译过程中分别以错误和告警的形式提醒开发人员注意相关代码设计问题,从而保证代码正确性。
    5 H' I1 ]% `4 u6 }" ^

* |" C: L/ l: Y9 T5 H) w0 u
  • 这样对于发布一些庞大的库代码时,为了让开发人员正确的使用库,这些提示会帮助他更好的移植代码。
    " e. `" |: Q( c" G- G+ Y0 d0 w6 [
# a4 n7 X/ B' ]
  • 那么经常有很多小伙伴编译出来的代码有一大堆warning,总是觉得warning关系不大,然而warning也是分不同类型的,对于一些未使用的变量倒关系不大,其他情况还是要认真对待,最好是做到"0 Error,0 warning".. I: @5 w7 g$ l9 e" [

    ; `) B8 o- r& L3 K8 T6 ]" S

; w' P/ P3 W2 U; U' c5 l& S  G
( U& [+ |/ Q( k4 I8 C# }
2
#undef

2 z9 O' c. [- w8 U  Y
   #undef标识符用于把前面的宏定义名取消,别看这宏用得不多,作用可大着呢,下面我简单举几个例子:
% Y# f1 i6 I8 `: S) J
1
局部宏定义

; |: Z# y( n, E
    一旦定义了宏,那么该文件中往下所有的代码都可以使用该宏,即使是函数内部,这样导致宏比较混乱,如下面代码:& R! y1 R  n" q- E% w
参考demo:
#include <stdio.h>
9 H3 w: h( l( s3 l/ H#include <stdlib.h>0 L. W) ^: }; A: ]
$ }6 R" O7 `, S7 F8 o0 ]* C% {7 t
#define configRatio 10
5 {' _3 C, X& |3 v& c
: e5 |3 \) a0 u( m/***************************************
7 f4 i" Q/ x$ h$ Z' \ * Fuction: 获得传感器电压值
% V' a* u7 _  C * Author 最后一个bug)
9 r5 Q9 H* z3 S6 u. `  s, T **************************************/
: A3 f5 x+ C) T/ Kint GetSensorVolt(void)
- ^$ v' m" a8 u2 X8 K" ^{    ' Z6 S! d0 J* J3 h. x/ h4 @# X/ N
#define configRatio 1
( U$ I$ K; C& n( s0 d    int ret = 0;
' @  G$ H1 b/ e# N$ Y     ret = configRatio*1024; //比例因子*AD值 ( o* Q+ y* [0 i: ~- q& P  m' u

. F9 _" a! Q+ o    return ret;
  {& Y' u# t# U* ]* G) L//#undef configRatio
4 f, @& Y8 S' t' f0 C& S6 B2 \}# @) f! h! [  T8 n5 }* A' f: \' J

' Y" v# d" `7 {/***************************************! |6 I( {) C) d( D6 C
* Fuction: 获得传感器电压值 3 s' P+ ^; D4 g! Z" M
* Author 最后一个bug) ; @8 B  g* K* D3 y
**************************************/
- a! U0 M) S( J* E8 Kint GetSensorCurr(void)
: i! s4 n5 _1 x) q{    : G: T9 D2 H7 W! P
#define configRatio 2  Y2 j' h. T2 g( a! Q6 t5 x
    int ret = 0;' {% |1 l( v" z
     ret = configRatio*1024; //比例因子*AD值
% k  d, _. \0 C9 C4 ]$ |
! J! Z( j9 Q& I$ L/ j4 N* R+ W% z4 U    return ret;; _' Z8 E5 X) s: b" m7 x
//#undef configRatio
8 e3 p' B& z& y( Z1 V, {3 x- h9 J6 F}
- J' x0 y$ i3 g0 n% h- X! L& p; X; H3 b/ }+ X
/***************************************9 [4 b3 j8 M3 {, l' s4 a
* Fuction: 进行预编译测试
7 h& [+ e# T- l! g" r; @5 o * Author 最后一个bug)
  o0 C6 v7 @) a8 ]2 w3 m3 V **************************************/. V+ w  V% B/ \2 x
int main(int argc, char *argv[]) {3 b. |1 I* _. c
. A: G' T$ g) ~
    printf("configRatio = %d\n",configRatio);  //报宏未定义 . K/ F' ^5 L! p3 V+ G
    printf("GetSensorVolt = %d\n",GetSensorVolt());  4 P2 L# r) D" Y; U9 h3 {: b( Z6 b3 f
    printf("GetSensorCurr = %d\n",GetSensorCurr());
: }& I. w5 `: p" Q* p    printf("公众号;最后一个bug\n");2 W, n: ?; {* t: _: X/ d
    return 0;
! y: T3 f0 s* N. v}
输出结果:
微信图片_20201028191403.png

5 F; N5 N; e% ^. o: B- N/ f7 j
微信图片_20201028191407.png
解释一下:
  • 假如我们没有注意到函数内部的同名宏定义,当然告警也没管,那么在main函数中使用同名宏定义就可能不是我们期待的最上面的宏定义,造成程序bug。

    6 s- X& q2 i/ E( u' Y" \
, J1 r* W3 X0 |5 K$ ~
  • 所以我们可以使用#undef来限制每个宏的作用域,如果每个函数内部都使用了#undef,那么main函数中再使用会报宏没有定义,这样便可以找到问题,当然也可以通过警告了解到。

    8 E8 m; x1 W4 y" C
0 t7 K6 k. [- b$ r4 [* \) [
2
选择接口
' l- o& i6 I4 P" G0 d! c
   通过宏来切换不同的接口供程序使用:
参考demo:
#include <stdio.h>
  M3 t% s* h1 A% B3 l#include <stdlib.h>. M6 w* F- N# B8 |8 r: ]) W
#include <string.h>+ B, z3 }* k/ m  u& X1 a( Q8 T. _1 s
9 Q# e- w) t: b; G! x- w8 x
  e6 W+ D/ B  m& P" g
#define DEV_SPI2 m' k: a6 ]3 o/ w2 [. M
#include "Drive.h"
) I- ?& J) r  x* N2 ~3 B# V4 B# h6 U#undef DEV_SPI- w; G4 t& d0 @; p0 G- P
/***************************************
5 g' p1 P& p& K7 W1 u9 v& p * Fuction: 进行预编译测试
( u* S1 W* s6 N, t* c * Author 最后一个bug)
0 S' _/ h7 W1 o4 \& Y8 ] **************************************/
5 B) ]% S5 D. g1 b6 d& Fint main(int argc, char *argv[]) {8 P/ m7 N8 k9 p4 L
1 m4 c6 V; j+ v% r8 D9 |, m4 W# z
    char *strbug = "the last bug" ;
! }0 ^, v- n) B. {& j" u# P% x
- ^2 M* \0 C' A) h6 c    SendData(strbug);: u1 e4 x' m2 W- |3 w% J
    ProcessData(strbug);7 v2 p, G+ k/ H$ T8 Q1 [; U" `  Q
    printf("公众号;最后一个bug\n");
6 ?% F2 z0 l, C    return 0;+ S& ~7 F* H: {. N% [, O
}
" K" F, w+ _8 a  m  q
& c) Y, _; L% A9 N1 `! s$ v' w
#include <stdio.h>
+ G# ~. Y# u4 `3 Y' @( ^, o6 b4 W+ w' V0 N- q) E% e/ g% C

: Y. H: r' R0 d. D, h#ifdef DEV_UART
; R+ }) P  p  D9 m. W4 W: ]/ N0 \& w: G
#define SendData(s)    printf("UART Send:%s\n",s)
* G' C" {/ C2 X$ V; E! P8 ?7 C5 F; C. G#define ProcessData(s)  printf("UART Process:%s\n",s)+ Y( f" Q3 L: H) L6 l

$ u' H; ?6 I" ~8 F* z$ i9 N  E#endif3 e: V* G- B8 r( S1 g+ n( b
5 |$ f$ A$ U9 C! Y4 O
#ifdef DEV_CAN3 g1 b" @5 H& I6 @! k

5 g6 i' L) U5 ~' h#define SendData(s)    printf("CAN Send:%s\n",s)# [6 W; m6 E: w$ U3 o8 {& m" e
#define ProcessData(s)  printf("CAN Process:%s\n",s)+ ?0 d' o5 e7 L
% n% [! z- |0 J6 v" Q
#endif
" s) L: V: l% d& w2 |, d' Y( T9 Q% c/ k1 Z
#ifdef DEV_SPI, x9 R# i8 e9 i8 I) v( E; d

2 _$ _! f4 }1 [2 N/ a#define SendData(s)    printf("Spi Send:%s\n",s)/ f9 w4 f# v0 J0 ]4 x/ E3 O8 V& ~
#define ProcessData(s)  printf("Spi Process:%s\n",s)
/ Z" w/ t- P- i( J' b' n
: ?) ~; d- s7 o# T% N#endif( Q/ S, o1 \1 K6 p! ?
输出结果:
# D* u) ]; X# u. R" X  _/ h: s3 Y
微信图片_20201028191413.png

& O1 L; D/ ?6 f4 o# d4 b
3
自定义接口

. Q4 w; I6 n$ I1 ]5 Y4 h
   当多个人维护一套代码的时候,有些同事喜欢调用库函数接口,而有些同事喜欢调用自定义接口,为了方便统一使用自定义接口或者库接口,我们会进行如下操作:
参考demo:
#include <stdio.h>
  x4 @' N' b$ `2 `" B6 v#include <stdlib.h>6 f' q6 s4 j2 ~1 T. v4 k
#include <string.h>
! ^- ]2 G6 Z- Y, b
! D$ [8 f* _- a! S% K#include "Drive.h"9 r7 H+ P( V1 D- V) X9 f9 V7 J; h; G
//#undef printf% D2 d. N4 g0 K6 x, N( P% c% i
3 E9 E8 K- S0 i) _
/***************************************
% l0 r* w( j+ Z& t * Fuction: 进行预编译测试 ( ?  I) `& p$ `
* Author 最后一个bug)
; z6 E$ a# E2 U% v( H **************************************/
  U& E% ?) p1 e/ f6 |: S2 [int main(int argc, char *argv[]) {
) J; ]7 y4 u: ^2 \! p6 u! f% b) O: e$ Q% K' M* Z3 n. p" I
    char *strbug = "the last bug" ;
7 j" M# t3 u+ {3 }
9 f8 J- {3 z$ ]* y: U    printf("公众号;最后一个bug\n");' o# L4 h8 j5 S
    return 0;0 ?8 d4 O# ?4 y
}+ ^- I6 k3 X# @, c2 d# o0 a0 h) s

: ^0 m6 M3 C8 j1 [0 O1 z
#ifndef __DRIVE_H__
) }* j: ~0 Q& j/ Q5 Z#define __DRIVE_H__
7 _& ^) B' r0 h1 Q9 s0 B
2 C' H* m' e  q, e# m* [: b#define  printf printf("please use Kprintf!\n");& }6 [4 ~, T3 }% Q
( i8 j* m+ y  o
extern void Kprintf(char *str);# d! G) I8 R* B, X

. ?4 A" \, C  L4 z#endif
输出结果:
微信图片_20201028191416.png

, _: ]7 P& E+ C& H0 w
  • 这样下面的代码你就只能够使用Kprintf来进行输出打印,而当我们放开注释掉的宏,这样就又可以使用printf了,还是比较方便的。

    1 \0 k6 ^2 C; M1 o8 ~+ G

, z# I) {# o" R# [

6 l2 @- J& t( }8 @* d) s; O9 h

4 e! j3 W6 L% S
咬金,懂了没 ?
小鲁班,这些知识都被你学到了!666

$ G+ e9 t% x6 P9 V/ k9 @
2、结束语
    上面这几个比较"冷门"的知识认真想想其实还是挺有用的,可能现在的产品都急于快速上市,对于代码的雕琢还有所欠缺的,一份成熟的代码不仅仅只是稳定,还有后期的维护、扩展等等都是值得考虑的。

( L/ f. p- S0 |. R3 h
微信图片_20201028191314.png
微信图片_20201028191325.png
微信图片_20201028191322.png
微信图片_20201028191335.png
微信图片_20201028191331.png
微信图片_20201028191340.png
微信图片_20201028191343.png
微信图片_20201028191419.png
微信图片_20201028191424.png
赞 收藏 评论0 发布时间:2020-10-28 19:20

举报

0个回答
关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
人形机器人运动控制、感知与智能配电
半导体创新技术与应用方向
EE架构与软件定义汽车
12V/48V 汽车智能配电(SPD)
区域控制单元(ZCU)与分区架构
关注我们
st-img 微信公众号
st-img 手机版