當(dāng)前位置:首頁 > 公眾號(hào)精選 > 嵌入式微處理器
[導(dǎo)讀]一位初學(xué)單片機(jī)的小伙伴讓我推薦C語言書籍,因?yàn)镃語言基礎(chǔ)比較差,想把C語言重新學(xué)一遍,再去學(xué)單片機(jī),我以前剛學(xué)單片機(jī)的時(shí)候也有這樣子的想法。其實(shí)C語言是可以邊學(xué)單片機(jī)邊學(xué)的,學(xué)單片機(jī)的一些例程中,遇到不懂的C語言知識(shí),再去查相關(guān)的知識(shí)點(diǎn),這樣印象才會(huì)深刻些。

說在前面的話

一位初學(xué)單片機(jī)的小伙伴讓我推薦C語言書籍,因?yàn)镃語言基礎(chǔ)比較差,想把C語言重新學(xué)一遍,再去學(xué)單片機(jī),我以前剛學(xué)單片機(jī)的時(shí)候也有這樣子的想法。

其實(shí)C語言是可以邊學(xué)單片機(jī)邊學(xué)的,學(xué)單片機(jī)的一些例程中,遇到不懂的C語言知識(shí),再去查相關(guān)的知識(shí)點(diǎn),這樣印象才會(huì)深刻些。

下面就列出了一些STM32中重要的C語言知識(shí)點(diǎn),初學(xué)的小伙伴可以多讀幾遍,其中大多知識(shí)點(diǎn)之前都有寫過,這里重新整理一下,更詳細(xì)地分析解釋可以閱讀附帶的鏈接。

assert_param

斷言(assert)就是用于在代碼中捕捉這些假設(shè),可以將斷言看作是異常處理的一種高級(jí)形式。

斷言表示為一些布爾表達(dá)式,程序員相信在程序中的某個(gè)特定點(diǎn)該表達(dá)式值為真。

可以在任何時(shí)候啟用和禁用斷言驗(yàn)證,因此可以在測(cè)試時(shí)啟用斷言,而在部署時(shí)禁用斷言。同樣,程序投入運(yùn)行后,最終用戶在遇到問題時(shí)可以重新啟用斷言。

注意assert()是一個(gè)宏,而不是函數(shù)。

在STM32中,常常會(huì)看到類似代碼:

assert_param(IS_ADC_ALL_INSTANCE(hadc->Instance));
assert_param(IS_ADC_SINGLE_DIFFERENTIAL(SingleDiff));

這是用來檢查函數(shù)傳入的參數(shù)的有效性。STM32中的assert_param默認(rèn)是不使用的,即:


如果要使用,需要定義USE_FULL_ASSERT宏,并且需要自己實(shí)現(xiàn)assert_failed函數(shù)。特別的,使用STM32CubeMX生成代碼的話,會(huì)在main.c生成:


我們?cè)谶@進(jìn)行填充就好。

下面分享一下assert的應(yīng)用例子:

//?公眾號(hào):嵌入式大雜燴
#include?
#include?
?
int?main(void)
{
?int?a,?b,?c;
?printf("請(qǐng)輸入b, c的值:");
?scanf("%d?%d",?&b,?&c);
?a?=?b?/?c;
?printf("a?=?%d",?a);
?return?0;
}

此處,變量c作為分母是不能等于0,如果我們輸入2 0,結(jié)果是什么呢?結(jié)果是程序會(huì)蹦:


這個(gè)例子中只有幾行代碼,我們很快就可以找到程序蹦的原因就是變量c的值為0。但是,如果代碼量很大,我們還能這么快的找到問題點(diǎn)嗎?

這時(shí)候,assert()就派上用場(chǎng)了,以上代碼中,我們可以在a = b / c;這句代碼之前加上assert(c);這句代碼用來判斷變量c的有效性。此時(shí),再編譯運(yùn)行,得到的結(jié)果為:


可見,程序蹦的同時(shí)還會(huì)在標(biāo)準(zhǔn)錯(cuò)誤流中打印一條錯(cuò)誤信息:

Assertion failed:c, file hello.c, line 12

這條信息包含了一些對(duì)我們查找bug很有幫助的信息:問題出在變量c,在hello.c文件的第12行。這么一來,我們就可以迅速的定位到問題點(diǎn)了。

這時(shí)候細(xì)心的朋友會(huì)發(fā)現(xiàn),上邊我們對(duì)assert()的介紹中,有這么一句說明:

如果表達(dá)式的值為假,assert()宏就會(huì)調(diào)用_assert函數(shù)在標(biāo)準(zhǔn)錯(cuò)誤流中打印一條錯(cuò)誤信息,并調(diào)用abort()(abort()函數(shù)的原型在stdlib.h頭文件中)函數(shù)終止程序。

所以,針對(duì)我們這個(gè)例子,我們的assert()宏我們也可以用以下代碼來代替:

if?(0?==?c)
{
?puts("c的值不能為0,請(qǐng)重新輸入!");
?abort();
}

這樣,也可以給我們起到提示的作用:


但是,使用assert()至少有幾個(gè)好處:

1)能自動(dòng)標(biāo)識(shí)文件和出問題的行號(hào)。

2)無需要更改代碼就能開啟或關(guān)閉assert機(jī)制(開不開啟關(guān)系到程序大小的問題)。如果認(rèn)為已經(jīng)排除了程序的bug,就可以把下面的宏定義寫在包含assert.h的位置的前面:

#define?NDEBUG

并重新編譯程序,這樣編輯器就會(huì)禁用工程文件中所有的assert()語句。如果程序又出現(xiàn)問題,可以移除這條#define指令(或把它注釋掉),然后重新編譯程序,這樣就可以重新啟用了assert()語句。

相關(guān)文章:【C語言筆記】assert()怎么用?

預(yù)處理指令

1、#error

#error?"Please?select?first?the?target?STM32L4xx?device?used?in?your?application?(in?stm32l4xx.h?file)"

#error 指令讓預(yù)處理器發(fā)出一條錯(cuò)誤信息,并且會(huì)中斷編譯過程。

#error的例子:

//?公眾號(hào):嵌入式大雜燴
#include?

#define??RX_BUF_IDX??100

#if?RX_BUF_IDX?==?0
static?const?unsigned?int?rtl8139_rx_config?=?0;
#elif?RX_BUF_IDX?==?1
static?const?unsigned?int?rtl8139_rx_config?=?1;
#elif?RX_BUF_IDX?==?2
static?const?unsigned?int?rtl8139_rx_config?=?2;
#elif?RX_BUF_IDX?==?3
static?const?unsigned?int?rtl8139_rx_config?=?3;
#else
#error?"Invalid?configuration?for?8139_RXBUF_IDX"
#endif

int?main(void)
{
?printf("hello?world\n");
?return?0;
}

這段示例代碼很簡單,當(dāng)RX_BUF_IDX宏的值不為0~3時(shí),在預(yù)處理階段就會(huì)通過#error 指令輸出一條錯(cuò)誤提示信息:

"Invalid configuration for 8139_RXBUF_IDX"

下面編譯看一看結(jié)果:

2、#if、#elif、#else、#endif、#ifdef、#ifndef

(1)#if

#if?(USE_HAL_ADC_REGISTER_CALLBACKS?==?1)
??void?(*?ConvCpltCallback)(struct?__ADC_HandleTypeDef?*hadc);?????????????
??//?......
#endif?/*?USE_HAL_ADC_REGISTER_CALLBACKS?*/

#if的使用一般使用格式如下

#if?整型常量表達(dá)式1
??程序段1
#elif?整型常量表達(dá)式2
??程序段2
#else
??程序段3
#endif

執(zhí)行起來就是,如果整形常量表達(dá)式為真,則執(zhí)行程序段1,以此類推,最后#endif是#if的結(jié)束標(biāo)志。

(2)#ifdef、#ifndef

#ifdef?HAL_RTC_MODULE_ENABLED
??#include?"stm32l4xx_hal_rtc.h"
#endif?/*?HAL_RTC_MODULE_ENABLED?*/

#ifdef的作用是判斷某個(gè)宏是否定義,如果該宏已經(jīng)定義則執(zhí)行后面的代碼,一般使用格式如下:

#ifdef??宏名
??程序段1
#else
??程序段2
#endif

它的意思是,如果該宏已被定義過,則對(duì)程序段1進(jìn)行編譯,否則對(duì)程序段2進(jìn)行編譯,通#if一樣,#endif也是#ifdef的結(jié)束標(biāo)志。

#ifndef?__STM32L4xx_HAL_ADC_EX_H
#define?__STM32L4xx_HAL_ADC_EX_H
//?......
#endif

#ifndef的作用與#ifdef的作用相反,用于判斷某個(gè)宏是否沒被定義。

(3)#if defined、#if !defined

defined用于判斷某個(gè)宏是否被定義, !defined與defined的作用相反。這樣一來#if defined可以達(dá)到與#ifdef一樣的效果。如例子:

#if?defined(STM32L412xx)
??#include?"stm32l412xx.h"
#elif?defined(STM32L422xx)
??#include?"stm32l422xx.h"
//........
#elif?defined(STM32L4S9xx)
??#include?"stm32l4s9xx.h"
#else
?#error?"Please?select?first?the?target?STM32L4xx?device?used?in?your?application?(in?stm32l4xx.h?file)"
#endif

如果STM32L412xx宏被定義,則包含頭文件stm32l412xx.h,以此類推。

既然已經(jīng)有#ifdef、#ifndef了,#if defined與#if !defined是否是多余的?

不是的,#ifdef和#ifndef僅能一次判斷一個(gè)宏名,而defined能做到一次判斷多個(gè)宏名,例如:

#if?defined(STM32L4R5xx)?||?defined(STM32L4R7xx)?||?defined(STM32L4R9xx)?||?defined(STM32L4S5xx)?||?defined(STM32L4S7xx)?||?defined(STM32L4S9xx)
//?......
#endif?/*?STM32L4R5xx?||?STM32L4R7xx?||?STM32L4R9xx?||?STM32L4S5xx?||?STM32L4S7xx?||?STM32L4S9xx?*/

更進(jìn)一步,可以構(gòu)建一些更密切地因果處理,如:

#if?defined(__ARMCC_VERSION)?&&?(__ARMCC_VERSION?
??#error?"Please?use?ARM?Compiler?Toolchain?V4.0.677?or?later!"
#endif
#define?PI?(3.14)
#define?R??(6)
?
#if?defined(PI)?&&?defined(R)?
#define?AREA?(PI*R*R)?
#endif

3、#pragma指令

#pragma指令為我們提供了讓編譯器執(zhí)行某些特殊操作提供了一種方法。這條指令對(duì)非常大的程序或需要使用特定編譯器的特殊功能的程序非常有用。

#pragma指令的一般形式為:#pragma para?,其中,para為參數(shù)。如

#if?defined?(?__GNUC__?)
#pragma?GCC?diagnostic?push
#pragma?GCC?diagnostic?ignored?"-Wsign-conversion"
#pragma?GCC?diagnostic?ignored?"-Wconversion"
#pragma?GCC?diagnostic?ignored?"-Wunused-parameter"
#endif

這一段的作用是忽略一些gcc的警告。#pragma命令中出現(xiàn)的命令集在不同的編譯器上是不一樣的,使用時(shí)必須查閱所使用的編譯器的文檔來了解有哪些命令、以及這些命令的功能。

下面簡單看一下#pragma命令的常見用法。

(1)、#pragma pack

我們可以利用#pragma pack來改變編譯器的對(duì)齊方式:

#pragma?pack(n)??/*?指定按n字節(jié)對(duì)齊?*/
#pragma?pack()???/*?取消自定義字節(jié)對(duì)齊?*/

我們使用#pragma pack指令來指定對(duì)齊的字節(jié)數(shù)。例子:

①指定按1字節(jié)對(duì)齊


運(yùn)行結(jié)果為:


②指定2字節(jié)對(duì)齊


運(yùn)行結(jié)果為:

可見,指定的對(duì)齊的字節(jié)數(shù)不一樣,得到的結(jié)果也不一樣。指定對(duì)齊有什么用呢,大概就是可以避免了移植過程中編譯器的差異帶來的代碼隱患吧。比如兩個(gè)編譯器的默認(rèn)對(duì)齊方式不一樣,那可能會(huì)帶來一些bug。

(2)#pragma message

該指令用于在預(yù)處理過程中輸出一些有用的提示信息,如:


運(yùn)行結(jié)果為:

如上,我們平時(shí)可以在一些條件編譯塊中加上類似信息,因?yàn)樵谝恍┖赀x擇較多的情況下,可能會(huì)導(dǎo)致代碼理解起來會(huì)混亂。不過現(xiàn)在一些編譯器、編輯器都會(huì)對(duì)這些情況進(jìn)行一些很明顯的區(qū)分了,比如哪塊代碼沒有用到,那塊代碼的背景色就會(huì)是灰色的。

(3)#pragma warning

該指令允許選擇性地修改編譯器警告信息。

例子:

#pragma?warning(?disable?:?4507?34;?once?:?4385;?error?:?164?)

等價(jià)于:

#pragma?warning(disable:4507?34)?//?不顯示4507和34號(hào)警告信息
#pragma?warning(once:4385)???????//?4385號(hào)警告信息僅報(bào)告一次
#pragma?warning(error:164)???????//?把164號(hào)警告信息作為一個(gè)錯(cuò)

這個(gè)指令暫且了解這么多,知道有這么一回事就可以。

關(guān)于#pragma指令還有很多用法,但比較冷門,這里暫且不列舉,有興趣的朋友可以自行學(xué)習(xí)。

相關(guān)文章:認(rèn)識(shí)認(rèn)識(shí)#pragma、#error指令

extern "C"

#ifndef?__STM32L4S7xx_H
#define?__STM32L4S7xx_H

#ifdef?__cplusplus
?extern?"C"?{
#endif?/*?__cplusplus?*/
?????
#ifdef?__cplusplus
}
#endif?/*?__cplusplus?*/

#endif?/*?__STM32L4S7xx_H?*/

加上extern "C"后,會(huì)指示編譯器這部分代碼按C語言(而不是C++)的方式進(jìn)行編譯。因?yàn)镃、C++編譯器對(duì)函數(shù)的編譯處理是不完全相同的,尤其對(duì)于C++來說,支持函數(shù)的重載,編譯后的函數(shù)一般是以函數(shù)名和形參類型來命名的。

例如函數(shù)void fun(int, int),編譯后的可能是_fun_int_int(不同編譯器可能不同,但都采用了類似的機(jī)制,用函數(shù)名和參數(shù)類型來命名編譯后的函數(shù)名);而C語言沒有類似的重載機(jī)制,一般是利用函數(shù)名來指明編譯后的函數(shù)名的,對(duì)應(yīng)上面的函數(shù)可能會(huì)是_fun這樣的名字。

相關(guān)文章:干貨 | extern "C"的用法解析

#與##運(yùn)算符

#define?__STM32_PIN(index,?gpio,?gpio_index)?\
{?\
index,?GPIO##gpio##_CLK_ENABLE,?GPIO##gpio,?GPIO_PIN_##gpio_index?\
}

1、#運(yùn)算符

我們平時(shí)使用帶參宏時(shí),字符串中的宏參數(shù)是沒有被替換的。例如:


輸出結(jié)果為:


然而,我們期望輸出的結(jié)果是:

5?+?20?=?25
13?+?14?=?27

這該怎么做呢?其實(shí),C語言允許在字符串中包含宏參數(shù)。在類函數(shù)宏(帶參宏)中,#號(hào)作為一個(gè)預(yù)處理運(yùn)算符,可以把記號(hào)轉(zhuǎn)換成字符串

例如,如果A是一個(gè)宏形參,那么#A就是轉(zhuǎn)換為字符串"A"的形參名。這個(gè)過程稱為字符串化(stringizing)。以下程序演示這個(gè)過程:


輸出結(jié)果為:


這就達(dá)到我們想要的結(jié)果了。所以,#運(yùn)算符可以完成字符串化(stringizing)的過程。

2、##運(yùn)算符

與#運(yùn)算符類似,##運(yùn)算符可用于類函數(shù)宏(帶參宏)的替換部分。##運(yùn)算符可以把兩個(gè)記號(hào)組合成一個(gè)記號(hào)。例如,可以這樣做:

#define?XNAME(n)?x##n

然后,宏XNAME(4)將展開x4。以下程序演示##運(yùn)算符的用法:


輸出結(jié)果為:

注意:PRINT_XN()宏用#運(yùn)算符組合字符串,##運(yùn)算符把記號(hào)組合為一個(gè)新的標(biāo)識(shí)符。

其實(shí),##運(yùn)算符在這里看來并沒有起到多大的便利,反而會(huì)讓我們感覺到不習(xí)慣。但是,使用##運(yùn)算符有時(shí)候是可以提高封裝性及程序的可讀性的。

相關(guān)文章:這兩個(gè)C運(yùn)算符你可能沒用過,但卻很有用~

_IO、 _I、 _O、volatile

一些底層結(jié)構(gòu)體成員中,常常使用_IO、 _O、 _I這三個(gè)宏來修飾,如:

typedef?struct
{

??__IO?uint32_t?TIR;??/*!
??__IO?uint32_t?TDTR;?/*!
??__IO?uint32_t?TDLR;?/*!
??__IO?uint32_t?TDHR;?/*!
}?CAN_TxMailBox_TypeDef;


而這三個(gè)宏其實(shí)是volatile的替換,即:

#define?????__I?????volatile?????????????/*!
#define?????__O?????volatile?????????????/*!
#define?????__IO????volatile?????????????/*!

volatile的作用就是不讓編譯器進(jìn)行優(yōu)化,即每次讀取或者修改值的時(shí)候,都必須重新從內(nèi)存或者寄存器中讀取或者修改。?在我們嵌入式中, volatile 用在如下的幾個(gè)地方:

  • 中斷服務(wù)程序中修改的供其它程序檢測(cè)的變量需要加 volatile;
  • 多任務(wù)環(huán)境下各任務(wù)間共享的標(biāo)志應(yīng)該加 volatile;
  • 存儲(chǔ)器映射的硬件寄存器通常也要加 volatile 說明,因?yàn)槊看螌?duì)它的讀寫都可能由不 同意義;

例如:

/*?假設(shè)REG為寄存器的地址?*/
uint32?*REG;
*REG?=?0;??/*?點(diǎn)燈?*/
*REG?=?1;??/*?滅燈?*/

此時(shí)若是REG不加volatile進(jìn)行修飾,則點(diǎn)燈操作將被優(yōu)化掉,只執(zhí)行滅燈操作。

位操作

STM32中,使用外設(shè)都得先配置其相關(guān)寄存器,都是使用一些位操作。比如庫函數(shù)的內(nèi)部實(shí)現(xiàn)就是一些位操作:

static?void?TI4_Config(TIM_TypeDef*?TIMx,?uint16_t?TIM_ICPolarity,?uint16_t?TIM_ICSelection,
???????????????????????uint16_t?TIM_ICFilter)

{
??uint16_t?tmpccmr2?=?0,?tmpccer?=?0,?tmp?=?0;

??/*?Disable?the?Channel?4:?Reset?the?CC4E?Bit?*/
??TIMx->CCER?&=?(uint16_t)~TIM_CCER_CC4E;
??tmpccmr2?=?TIMx->CCMR2;
??tmpccer?=?TIMx->CCER;
??tmp?=?(uint16_t)(TIM_ICPolarity?<12);

??/*?Select?the?Input?and?set?the?filter?*/
??tmpccmr2?&=?((uint16_t)~TIM_CCMR1_CC2S)?&?((uint16_t)~TIM_CCMR1_IC2F);
??tmpccmr2?|=?(uint16_t)(TIM_ICSelection?<8);
??tmpccmr2?|=?(uint16_t)(TIM_ICFilter?<12);

??/*?Select?the?Polarity?and?set?the?CC4E?Bit?*/
??tmpccer?&=?(uint16_t)~(TIM_CCER_CC4P?|?TIM_CCER_CC4NP);
??tmpccer?|=?(uint16_t)(tmp?|?(uint16_t)TIM_CCER_CC4E);

??/*?Write?to?TIMx?CCMR2?and?CCER?registers?*/
??TIMx->CCMR2?=?tmpccmr2;
??TIMx->CCER?=?tmpccer?;
}

看似很復(fù)雜,其實(shí)就是按照規(guī)格書來配置就可以。雖然實(shí)際應(yīng)用中,很少會(huì)采用直接配置寄存器的方法來使用,但是也需要掌握,一些特殊的地方可以直接操控寄存器,比如中斷中。

位操作簡單例子:

首先,以下是按位運(yùn)算符:


嵌入式編程中,常常需要對(duì)一些寄存器進(jìn)行配置,有的情況下需要改變一個(gè)字節(jié)中的某一位或者幾位,但是又不想改變其它位原有的值,這時(shí)就可以使用按位運(yùn)算符進(jìn)行操作。下面進(jìn)行舉例說明,假如有一個(gè)8位的TEST寄存器:


當(dāng)我們要設(shè)置第0位bit0的值為1時(shí),可能會(huì)這樣進(jìn)行設(shè)置:

TEST?=?0x01;

但是,這樣設(shè)置是不夠準(zhǔn)確的,因?yàn)檫@時(shí)候已經(jīng)同時(shí)操作到了高7位:bit1~bit7,如果這高7位沒有用到的話,這么設(shè)置沒有什么影響;但是,如果這7位正在被使用,結(jié)果就不是我們想要的了。

在這種情況下,我們就可以借用按位操作運(yùn)算符進(jìn)行配置。

對(duì)于二進(jìn)制位操作來說,不管該位原來的值是0還是1,它跟0進(jìn)行&運(yùn)算,得到的結(jié)果都是0,而跟1進(jìn)行&運(yùn)算,將保持原來的值不變;不管該位原來的值是0還是1,它跟1進(jìn)行|運(yùn)算,得到的結(jié)果都是1,而跟0進(jìn)行|運(yùn)算,將保持原來的值不變。

所以,此時(shí)可以設(shè)置為:

TEST?=?TEST?|?0x01;

其意義為:TEST寄存器的高7位均不變,最低位變成1了。在實(shí)際編程中,常改寫為:

TEST?|=?0x01;

這種寫法可以一定程度上簡化代碼,是 C 語言常用的一種編程風(fēng)格。設(shè)置寄存器的某一位還有另一種操作方法,以上的等價(jià)方法如:

TEST?|=?(0x01?<

第幾位要置1就左移幾位。

同樣的,要給TEST的低4位清0,高4位保持不變,可以進(jìn)行如下配置:

TEST?&=?0xF0;

相關(guān)文章:C語言、嵌入式位操作精華技巧大匯總

do {}while(0)

這是在宏定義中用的,STM32的標(biāo)準(zhǔn)庫中沒有使用這種用法,HAL庫中有大量的用法例子,如:

#define?__HAL_FLASH_INSTRUCTION_CACHE_RESET()???do?{?SET_BIT(FLASH->ACR,?FLASH_ACR_ICRST);???\
?????????????????????????????????????????????????????CLEAR_BIT(FLASH->ACR,?FLASH_ACR_ICRST);?\
???????????????????????????????????????????????????}?while?(0)

下面以一個(gè)例子來分析do {}while(0)的用法:

//?公眾號(hào):嵌入式大雜燴
#define??DEBUG???1??

#if?DEBUG
??#define?DBG_PRINTF(fmt,?args...)??\
??{\
????printf("<>?",?__FILE__,?__LINE__,?__FUNCTION__);\
????printf(fmt,?##args);\
??}

#else
??#define?DBG_PRINTF(fmt,?args...)???
#endif

這個(gè)宏打印有什么缺陷?

我們與if、else使用的時(shí)候,會(huì)有這樣的一種使用情況:


此時(shí)會(huì)報(bào)語法錯(cuò)誤。為什么呢?

同樣的,我們可以先來看一下我們的demo代碼預(yù)處理過后,相應(yīng)的宏代碼會(huì)被轉(zhuǎn)換為什么。如:


這里我們可以看到,我們的if、else結(jié)構(gòu)代碼被替換為如下形式:

if(c)
{?/*?.......?*/?};
else
{?/*?.......?*/?};

顯然,出現(xiàn)了語法錯(cuò)誤。if之后的大括號(hào)之后不能加分號(hào),這里的分號(hào)其實(shí)可以看做一條空語句,這個(gè)空語句會(huì)把if與else給分隔開來,導(dǎo)致else不能正確匹配到if,導(dǎo)致語法錯(cuò)誤。

為了解決這個(gè)問題,有幾種方法。第一種方法是:把分號(hào)去掉。代碼變成:


第二種方法是:在if之后使用DBG_PRINTF打印調(diào)試時(shí)總是加{}。代碼變成:


以上兩種方法都可以正常編譯、運(yùn)行了。

但是,我們C語言中,每條語句往往以分號(hào)結(jié)尾;并且,總有些人習(xí)慣在if判斷之后只有一條語句的情況下不加大括號(hào);而且我們創(chuàng)建的DBG_PRINTF宏函數(shù)的目的就是為了對(duì)標(biāo)printf函數(shù),printf函數(shù)的使用加分號(hào)在任何地方的使用都是沒有問題的。

基于這幾個(gè)原因,我們有必要再對(duì)我們的DBG_PRINTF宏函數(shù)進(jìn)行一個(gè)改造。

下面引入do{}while(0)來對(duì)我們的DBG_PRINTF進(jìn)行一個(gè)簡單的改造。改造后的DBG_PRINTF宏函數(shù)如下:

#define?DBG_PRINTF(fmt,?args...)??\
do\
{\
????printf("<>?",?__FILE__,?__LINE__,?__FUNCTION__);\
????printf(fmt,?##args);\
}while(0)

這里的do...while循環(huán)的循環(huán)體只執(zhí)行一次,與不加循環(huán)是效果一樣。并且,可以避免了上面的問題。預(yù)處理文件:


我們的宏函數(shù)實(shí)體中,while(0)后面不加分號(hào),在實(shí)際調(diào)用時(shí)補(bǔ)上分號(hào),既符合了C語言語句分號(hào)結(jié)尾的習(xí)慣,也符合了do...while的語法規(guī)則。

使用do{}while(0)來封裝宏函數(shù)可能會(huì)讓很多初學(xué)者看著不習(xí)慣,但必須承認(rèn)的是,這確確實(shí)實(shí)是一種很常用的方法。

推薦文章:C語言、嵌入式中幾個(gè)非常實(shí)用的宏技巧

static與extern

1、static


static主要有三種用法:在函數(shù)內(nèi)用于修飾變量、用于修飾函數(shù)、用于修飾本.c文件全局變量。后兩個(gè)容易理解,用于修飾函數(shù)與全局變量表明變量與函數(shù)在本模塊內(nèi)使用。

下面看看static在函數(shù)內(nèi)用于修飾變量的例子:

//?公眾號(hào):嵌入式大雜燴
#include?

void?test(void)
{
????int?normal_var?=?0;
????static?int?static_var?=?0;

????printf("normal_var:%d??static_var:%d\n",?normal_var,?static_var);
????normal_var++;
????static_var++;
}

int?main(void)
{
?????int?i;

?????for?(?i?=?0;?i?3;?i++)
?????{
???????test();
?????}

?????return?0;
}

運(yùn)行結(jié)果:


可以看出,函數(shù)每次被調(diào)用,普通局部變量都是重新分配,而static修飾的變量保持上次調(diào)用的值不變,即只被初始化一次。

2、extern

extern的用法簡單,用于聲明多個(gè)模塊共享的全局變量、聲明外部函數(shù)。


END

來源:嵌入式大雜燴,作者:里米君

版權(quán)歸原作者所有,如有侵權(quán),請(qǐng)聯(lián)系刪除。

推薦閱讀
國內(nèi)MCU能替代國外產(chǎn)品嗎?MCU的未來又將如何?
STM32價(jià)格瘋長下,盤點(diǎn)STM32的國產(chǎn)替代者
選微處理器MPU,還是單片機(jī)MCU?兩者區(qū)別詳解

免責(zé)聲明:本文內(nèi)容由21ic獲得授權(quán)后發(fā)布,版權(quán)歸原作者所有,本平臺(tái)僅提供信息存儲(chǔ)服務(wù)。文章僅代表作者個(gè)人觀點(diǎn),不代表本平臺(tái)立場(chǎng),如有問題,請(qǐng)聯(lián)系我們,謝謝!

嵌入式ARM

掃描二維碼,關(guān)注更多精彩內(nèi)容

本站聲明: 本文章由作者或相關(guān)機(jī)構(gòu)授權(quán)發(fā)布,目的在于傳遞更多信息,并不代表本站贊同其觀點(diǎn),本站亦不保證或承諾內(nèi)容真實(shí)性等。需要轉(zhuǎn)載請(qǐng)聯(lián)系該專欄作者,如若文章內(nèi)容侵犯您的權(quán)益,請(qǐng)及時(shí)聯(lián)系本站刪除。
換一批
延伸閱讀

9月2日消息,不造車的華為或?qū)⒋呱龈蟮莫?dú)角獸公司,隨著阿維塔和賽力斯的入局,華為引望愈發(fā)顯得引人矚目。

關(guān)鍵字: 阿維塔 塞力斯 華為

倫敦2024年8月29日 /美通社/ -- 英國汽車技術(shù)公司SODA.Auto推出其旗艦產(chǎn)品SODA V,這是全球首款涵蓋汽車工程師從創(chuàng)意到認(rèn)證的所有需求的工具,可用于創(chuàng)建軟件定義汽車。 SODA V工具的開發(fā)耗時(shí)1.5...

關(guān)鍵字: 汽車 人工智能 智能驅(qū)動(dòng) BSP

北京2024年8月28日 /美通社/ -- 越來越多用戶希望企業(yè)業(yè)務(wù)能7×24不間斷運(yùn)行,同時(shí)企業(yè)卻面臨越來越多業(yè)務(wù)中斷的風(fēng)險(xiǎn),如企業(yè)系統(tǒng)復(fù)雜性的增加,頻繁的功能更新和發(fā)布等。如何確保業(yè)務(wù)連續(xù)性,提升韌性,成...

關(guān)鍵字: 亞馬遜 解密 控制平面 BSP

8月30日消息,據(jù)媒體報(bào)道,騰訊和網(wǎng)易近期正在縮減他們對(duì)日本游戲市場(chǎng)的投資。

關(guān)鍵字: 騰訊 編碼器 CPU

8月28日消息,今天上午,2024中國國際大數(shù)據(jù)產(chǎn)業(yè)博覽會(huì)開幕式在貴陽舉行,華為董事、質(zhì)量流程IT總裁陶景文發(fā)表了演講。

關(guān)鍵字: 華為 12nm EDA 半導(dǎo)體

8月28日消息,在2024中國國際大數(shù)據(jù)產(chǎn)業(yè)博覽會(huì)上,華為常務(wù)董事、華為云CEO張平安發(fā)表演講稱,數(shù)字世界的話語權(quán)最終是由生態(tài)的繁榮決定的。

關(guān)鍵字: 華為 12nm 手機(jī) 衛(wèi)星通信

要點(diǎn): 有效應(yīng)對(duì)環(huán)境變化,經(jīng)營業(yè)績穩(wěn)中有升 落實(shí)提質(zhì)增效舉措,毛利潤率延續(xù)升勢(shì) 戰(zhàn)略布局成效顯著,戰(zhàn)新業(yè)務(wù)引領(lǐng)增長 以科技創(chuàng)新為引領(lǐng),提升企業(yè)核心競(jìng)爭(zhēng)力 堅(jiān)持高質(zhì)量發(fā)展策略,塑強(qiáng)核心競(jìng)爭(zhēng)優(yōu)勢(shì)...

關(guān)鍵字: 通信 BSP 電信運(yùn)營商 數(shù)字經(jīng)濟(jì)

北京2024年8月27日 /美通社/ -- 8月21日,由中央廣播電視總臺(tái)與中國電影電視技術(shù)學(xué)會(huì)聯(lián)合牽頭組建的NVI技術(shù)創(chuàng)新聯(lián)盟在BIRTV2024超高清全產(chǎn)業(yè)鏈發(fā)展研討會(huì)上宣布正式成立。 活動(dòng)現(xiàn)場(chǎng) NVI技術(shù)創(chuàng)新聯(lián)...

關(guān)鍵字: VI 傳輸協(xié)議 音頻 BSP

北京2024年8月27日 /美通社/ -- 在8月23日舉辦的2024年長三角生態(tài)綠色一體化發(fā)展示范區(qū)聯(lián)合招商會(huì)上,軟通動(dòng)力信息技術(shù)(集團(tuán))股份有限公司(以下簡稱"軟通動(dòng)力")與長三角投資(上海)有限...

關(guān)鍵字: BSP 信息技術(shù)
關(guān)閉
關(guān)閉