RM新时代网站-首页

0
  • 聊天消息
  • 系統(tǒng)消息
  • 評論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

STM32中重要的C語言知識點(diǎn)總結(jié)

GReq_mcu168 ? 來源:嵌入式大雜燴 ? 作者:嵌入式大雜燴 ? 2021-04-25 16:42 ? 次閱讀

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

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

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

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

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

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

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

在STM32中,常常會看到類似代碼:

assert_param(IS_ADC_ALL_INSTANCE(hadc-》Instance));

assert_param(IS_ADC_SINGLE_DIFFERENTIAL(SingleDiff));

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

30593072-a4ba-11eb-aece-12bb97331649.png

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

30b1c80e-a4ba-11eb-aece-12bb97331649.png

我們在這進(jìn)行填充就好。

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

// 公眾號:嵌入式大雜燴

#include 《stdio.h》

#include 《assert.h》

int main(void)

{

int a, b, c;

printf(“請輸入b, c的值:”);

scanf(“%d %d”, &b, &c);

a = b / c;

printf(“a = %d”, a);

return 0;

}

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

30e172a2-a4ba-11eb-aece-12bb97331649.png

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

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

30ed3fa6-a4ba-11eb-aece-12bb97331649.png

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

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

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

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

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

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

if (0 == c)

{

puts(“c的值不能為0,請重新輸入!”);

abort();

}

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

30f76bf2-a4ba-11eb-aece-12bb97331649.png

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

1)能自動標(biāo)識文件和出問題的行號。

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

#define NDEBUG

并重新編譯程序,這樣編輯器就會禁用工程文件中所有的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ā)出一條錯誤信息,并且會中斷編譯過程。

#error的例子:

// 公眾號:嵌入式大雜燴

#include 《stdio.h》

#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

”);

return 0;

}

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

“Invalid configuration for 8139_RXBUF_IDX”

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

3122a1dc-a4ba-11eb-aece-12bb97331649.png

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的作用是判斷某個宏是否定義,如果該宏已經(jīng)定義則執(zhí)行后面的代碼,一般使用格式如下:

#ifdef 宏名

程序段1

#else

程序段2

#endif

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

#ifndef __STM32L4xx_HAL_ADC_EX_H

#define __STM32L4xx_HAL_ADC_EX_H

// 。..。..

#endif

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

(3)#if defined、#if !defined

defined用于判斷某個宏是否被定義, !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僅能一次判斷一個宏名,而defined能做到一次判斷多個宏名,例如:

#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 《 400677)

#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í)行某些特殊操作提供了一種方法。這條指令對非常大的程序或需要使用特定編譯器的特殊功能的程序非常有用。

#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)的命令集在不同的編譯器上是不一樣的,使用時必須查閱所使用的編譯器的文檔來了解有哪些命令、以及這些命令的功能。

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

(1)、#pragma pack

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

#pragma pack(n) /* 指定按n字節(jié)對齊 */

#pragma pack() /* 取消自定義字節(jié)對齊 */

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

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

312d64d2-a4ba-11eb-aece-12bb97331649.png

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

3257db12-a4ba-11eb-aece-12bb97331649.png

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

32948b02-a4ba-11eb-aece-12bb97331649.png

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

32bd26d4-a4ba-11eb-aece-12bb97331649.png

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

(2)#pragma message

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

32c907ec-a4ba-11eb-aece-12bb97331649.png

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

33169926-a4ba-11eb-aece-12bb97331649.png

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

(3)#pragma warning

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

例子:

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

等價于:

#pragma warning(disable:4507 34) // 不顯示4507和34號警告信息

#pragma warning(once:4385) // 4385號警告信息僅報告一次

#pragma warning(error:164) // 把164號警告信息作為一個錯

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

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

相關(guān)文章:認(rèn)識認(rèn)識#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”后,會指示編譯器這部分代碼按C語言(而不是C++)的方式進(jìn)行編譯。因?yàn)镃、C++編譯器對函數(shù)的編譯處理是不完全相同的,尤其對于C++來說,支持函數(shù)的重載,編譯后的函數(shù)一般是以函數(shù)名和形參類型來命名的。

例如函數(shù)void fun(int, int),編譯后的可能是_fun_int_int(不同編譯器可能不同,但都采用了類似的機(jī)制,用函數(shù)名和參數(shù)類型來命名編譯后的函數(shù)名);而C語言沒有類似的重載機(jī)制,一般是利用函數(shù)名來指明編譯后的函數(shù)名的,對應(yīng)上面的函數(shù)可能會是_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ù)是沒有被替換的。例如:

3334d562-a4ba-11eb-aece-12bb97331649.jpg

輸出結(jié)果為:

334f86d2-a4ba-11eb-aece-12bb97331649.jpg

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

5 + 20 = 25

13 + 14 = 27

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

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

335861c6-a4ba-11eb-aece-12bb97331649.jpg

輸出結(jié)果為:

367254a2-a4ba-11eb-aece-12bb97331649.jpg

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

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

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

#define XNAME(n) x##n

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

3692ca48-a4ba-11eb-aece-12bb97331649.jpg

輸出結(jié)果為:

369c37b8-a4ba-11eb-aece-12bb97331649.jpg

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

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

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

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

typedef struct

{

__IO uint32_t TIR; /*!《 CAN TX mailbox identifier register */

__IO uint32_t TDTR; /*!《 CAN mailbox data length control and time stamp register */

__IO uint32_t TDLR; /*!《 CAN mailbox data low register */

__IO uint32_t TDHR; /*!《 CAN mailbox data high register */

} CAN_TxMailBox_TypeDef;

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

#define __I volatile /*!《 Defines ‘read only’ permissions */

#define __O volatile /*!《 Defines ‘write only’ permissions */

#define __IO volatile /*!《 Defines ‘read / write’ permissions */

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

中斷服務(wù)程序中修改的供其它程序檢測的變量需要加 volatile;

多任務(wù)環(huán)境下各任務(wù)間共享的標(biāo)志應(yīng)該加 volatile;

存儲器映射的硬件寄存器通常也要加 volatile 說明,因?yàn)槊看螌λ淖x寫都可能由不 同意義;

例如:

/* 假設(shè)REG為寄存器的地址 */

uint32 *REG;

*REG = 0; /* 點(diǎn)燈 */

*REG = 1; /* 滅燈 */

此時若是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)用中,很少會采用直接配置寄存器的方法來使用,但是也需要掌握,一些特殊的地方可以直接操控寄存器,比如中斷中。

位操作簡單例子:

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

36ba9168-a4ba-11eb-aece-12bb97331649.png

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

36eea9ee-a4ba-11eb-aece-12bb97331649.png

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

TEST = 0x01;

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

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

對于二進(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è)置為:

TEST = TEST | 0x01;

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

TEST |= 0x01;

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

TEST |= (0x01 《《 0);

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

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

TEST &= 0xF0;

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)

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

// 公眾號:嵌入式大雜燴

#define DEBUG 1

#if DEBUG

#define DBG_PRINTF(fmt, args.。.)

{

printf(“《《File:%s Line:%d Function:%s》》 ”, __FILE__, __LINE__, __FUNCTION__);

printf(fmt, ##args);

}

#else

#define DBG_PRINTF(fmt, args.。.)

#endif

這個宏打印有什么缺陷?

我們與if、else使用的時候,會有這樣的一種使用情況:

36fbd48e-a4ba-11eb-aece-12bb97331649.png

此時會報語法錯誤。為什么呢?

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

3706fbb6-a4ba-11eb-aece-12bb97331649.png

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

if(c)

{ /* 。..。..。 */ };

else

{ /* 。..。..。 */ };

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

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

376a6c1e-a4ba-11eb-aece-12bb97331649.png

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

377f61a0-a4ba-11eb-aece-12bb97331649.png

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

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

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

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

#define DBG_PRINTF(fmt, args.。.)

do

{

printf(“《《File:%s Line:%d Function:%s》》 ”, __FILE__, __LINE__, __FUNCTION__);

printf(fmt, ##args);

}while(0)

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

37991c30-a4ba-11eb-aece-12bb97331649.png

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

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

static與extern1、static

37be1224-a4ba-11eb-aece-12bb97331649.png

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

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

// 公眾號:嵌入式大雜燴

#include 《stdio.h》

void test(void)

{

int normal_var = 0;

static int static_var = 0;

printf(“normal_var:%d static_var:%d

”, 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é)果:

37c9ae18-a4ba-11eb-aece-12bb97331649.jpg

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

2、extern

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

37d79f78-a4ba-11eb-aece-12bb97331649.png

原文標(biāo)題:STM32中C語言知識點(diǎn):初學(xué)者必看,老鳥復(fù)習(xí)(長文總結(jié))

文章出處:【微信公眾號:玩轉(zhuǎn)單片機(jī)】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。

責(zé)任編輯:haq

聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點(diǎn)僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問題,請聯(lián)系本站處理。 舉報投訴
  • STM32
    +關(guān)注

    關(guān)注

    2270

    文章

    10895

    瀏覽量

    355727
  • C語言
    +關(guān)注

    關(guān)注

    180

    文章

    7604

    瀏覽量

    136683

原文標(biāo)題:STM32中C語言知識點(diǎn):初學(xué)者必看,老鳥復(fù)習(xí)(長文總結(jié))

文章出處:【微信號:mcu168,微信公眾號:硬件攻城獅】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。

收藏 人收藏

    評論

    相關(guān)推薦

    《DNESP32S3使用指南-IDF版_V1.6》第二章 常用的C語言知識點(diǎn)

    第二章常用的C語言知識點(diǎn) 本章將為大家介紹常用的C語言知識點(diǎn)。對于已經(jīng)熟練掌握
    發(fā)表于 11-21 09:26

    接口測試?yán)碚摗⒁蓡柺珍浥c擴(kuò)展相關(guān)知識點(diǎn)

    本文章使用王者榮耀游戲接口、企業(yè)微信接口的展示結(jié)合理論知識,講解什么是接口測試、接口測試?yán)碚?、疑問收錄與擴(kuò)展相關(guān)知識點(diǎn)知識學(xué)院,快來一起看看吧~
    的頭像 發(fā)表于 11-15 09:12 ?306次閱讀
    接口測試?yán)碚?、疑問收錄與擴(kuò)展相關(guān)<b class='flag-5'>知識點(diǎn)</b>

    技術(shù)干貨驛站 ▏深入理解C語言:掌握程序結(jié)構(gòu)知識

    在計算機(jī)編程的世界,C語言被廣泛認(rèn)可為一門強(qiáng)大而高效的編程語言,其簡潔的語法和直接的指令使得它成為了許多程序員的首選。了解C
    的頭像 發(fā)表于 07-27 08:45 ?1344次閱讀
    技術(shù)干貨驛站 ▏深入理解<b class='flag-5'>C</b><b class='flag-5'>語言</b>:掌握程序結(jié)構(gòu)<b class='flag-5'>知識</b>

    【《大語言模型應(yīng)用指南》閱讀體驗(yàn)】+ 基礎(chǔ)篇

    講解,包括偏置、權(quán)重、激活函數(shù);三要素包括網(wǎng)絡(luò)結(jié)構(gòu)、損失函數(shù)和優(yōu)化方法。章節(jié)最后總結(jié)了神經(jīng)網(wǎng)絡(luò)參數(shù)學(xué)習(xí)的關(guān)鍵步驟。 1.4章節(jié)描述了自然語言處理的相關(guān)知識點(diǎn),包括什么是自然語言處理、文
    發(fā)表于 07-25 14:33

    C++語言基礎(chǔ)知識

    電子發(fā)燒友網(wǎng)站提供《C++語言基礎(chǔ)知識.pdf》資料免費(fèi)下載
    發(fā)表于 07-19 10:58 ?7次下載

    模擬電子技術(shù)知識點(diǎn)問題總結(jié)概覽

    給大家分享模擬電子技術(shù)知識點(diǎn)問題總結(jié)
    的頭像 發(fā)表于 05-08 15:16 ?1148次閱讀
    模擬電子技術(shù)<b class='flag-5'>知識點(diǎn)</b>問題<b class='flag-5'>總結(jié)</b>概覽

    基于FPGA的常見的圖像算法模塊總結(jié)

    意在給大家補(bǔ)充一下基于FPGA的圖像算法基礎(chǔ),于是講解了一下常見的圖像算法模塊,經(jīng)過個人的總結(jié),將知識點(diǎn)分布如下所示。
    的頭像 發(fā)表于 04-28 11:45 ?588次閱讀
    基于FPGA的常見的圖像算法模塊<b class='flag-5'>總結(jié)</b>

    如何成為一名嵌入式C語言高手?

    。 三、通過實(shí)踐項(xiàng)目提升技能理論知識是建立在實(shí)踐基礎(chǔ)之上的。選擇一些小型的嵌入式項(xiàng)目,例如LED閃爍、溫度監(jiān)測等簡單的應(yīng)用,將所學(xué)的C語言知識應(yīng)用到實(shí)際
    發(fā)表于 04-07 16:03

    一篇搞定DCS系統(tǒng)相關(guān)知識點(diǎn)

    目標(biāo)。DCS系統(tǒng)廣泛應(yīng)用于各個行業(yè),如化工、電力、制藥等。在這些行業(yè),DCS系統(tǒng)可以實(shí)現(xiàn)對生產(chǎn)過程的集中監(jiān)控和分散控制,提高生產(chǎn)效率和產(chǎn)品質(zhì)量,降低能耗和減少環(huán)境污染,從而保證產(chǎn)品質(zhì)量,并確保生產(chǎn)過程的安全可靠。 二.DCS系統(tǒng)知識點(diǎn)
    的頭像 發(fā)表于 03-26 18:40 ?883次閱讀
    一篇搞定DCS系統(tǒng)相關(guān)<b class='flag-5'>知識點(diǎn)</b>

    如何成為一名嵌入式C語言高手?

    。 三、通過實(shí)踐項(xiàng)目提升技能理論知識是建立在實(shí)踐基礎(chǔ)之上的。選擇一些小型的嵌入式項(xiàng)目,例如LED閃爍、溫度監(jiān)測等簡單的應(yīng)用,將所學(xué)的C語言知識應(yīng)用到實(shí)際
    發(fā)表于 03-25 14:12

    【量子計算機(jī)重構(gòu)未來 | 閱讀體驗(yàn)】第二章關(guān)鍵知識點(diǎn)

    本帖最后由 oxlm_1 于 2024-3-6 23:20 編輯 之所以將第二章單獨(dú)拿出來,是因?yàn)樵陂喿x過程,發(fā)現(xiàn)第二章知識點(diǎn)較多,理解起來比較耗時間。 第二章的主要知識點(diǎn): 量子
    發(fā)表于 03-06 23:17

    機(jī)器視覺基礎(chǔ)知識點(diǎn)總結(jié)

    照明是影響機(jī)器視覺系統(tǒng)輸入的重要因素。光源系統(tǒng)的設(shè)計非常重要,它與輸入數(shù)據(jù)直接相關(guān),即圖像的質(zhì)量和應(yīng)用效果。
    發(fā)表于 01-16 10:08 ?1112次閱讀

    什么是分割?圖像分割知識點(diǎn)總結(jié)

    盡管FCN意義重大,在當(dāng)時來講效果也相當(dāng)驚人,但是FCN本身仍然有許多局限。
    的頭像 發(fā)表于 01-13 15:53 ?2102次閱讀
    什么是分割?圖像分割<b class='flag-5'>知識點(diǎn)</b><b class='flag-5'>總結(jié)</b>

    詳解SystemVueAtoD模型的help文件

    看懂軟件ADC模型的help文件,需要懂的知識點(diǎn)也是不少的哈!
    的頭像 發(fā)表于 12-26 17:06 ?640次閱讀
    詳解SystemVue<b class='flag-5'>中</b>AtoD模型的help文件

    淺談初級電工必備知識點(diǎn)

    對于初學(xué)電工的朋友來說,掌握一些基礎(chǔ)且實(shí)用的知識點(diǎn)是非常重要的。本文旨在分享初級電工應(yīng)該掌握的核心知識,幫助新手電工更好地入門和提升技能。
    的頭像 發(fā)表于 12-26 10:44 ?1091次閱讀
    RM新时代网站-首页