編程學習過程中越是痛苦的時候,學到的東西就會越多

2022-05-24     大方老師單片機

原標題:編程學習過程中越是痛苦的時候,學到的東西就會越多

STM32RTC開發中,編程學習過程中越是痛苦的時候,學到的東西就會越多

1.結構體有何作用

三個月前,教研室里一個學長在華為南京研究院的面試中就遇到這個問題。當然,這只是面試中最基礎的問題。如果問你你怎麼回答?我的理解是這樣的C語言中結構體至少有以下三個作用:

(1)有機地組織了對象的屬性。

比如,STM32RTC開發中,我們需要數據來表示日期和時間,這些數據通常是年、月、日、時、分、秒。如果我們不用結構體,那麼就需要定6個變量來表示。這樣的話程序的數據結構是鬆散的,我們的數據結構最好高內聚,低耦的。所以,用一個結構體來表示更好,無論是從程序的可讀性還是可移植性還是可維護性皆是:

typedef struct //公曆日期和時間結構體

{

vu16 year;

vu8 month;

vu8 date;

vu8 hour;

vu8 min;

vu8 sec;

}_calendar_obj;

_calendar_obj calendar; //定義結構體變量

(2)以修改結構體成員變量的方法代替了函(入口參)的重新定義。

如果說結構體有機地組織了對象的屬性表示結構,那麼以修改結構體成員變量的方法代替函(入口參)的重新定義就表示了結構。繼續以上面的結構體為例子,我們來分析。假如現在我有如下函數來顯示日期和時間:

void DsipDateTime( _calendar_obj DateTimeVal)

那麼我們只要將一_calendar_obj這個結構體類型的變量作為實參調DsipDateTime()即可DsipDateTime()DateTimeVal的成變量來實現內容的顯示。如果不用結構體,我們很可能需要寫這樣的一個函數:

void DsipDateTime( vu16 yearvu8 monthvu8 datevu8 hourvu8 minvu8 sec)

顯然這樣的形參很不可觀,數據結構管理起來也很繁瑣。如果某個函數的返回值得是一個表示日期和時間的數據,那就更複雜了。這只是一方面。

另一方面,如果用戶需要表示日期和時間的數據中還要包含星(),這個時候,如果之前沒有用機構體,那麼應該DsipDateTime()函數中在增加一個形vu8 week

void DsipDateTime( vu16 yearvu8 monthvu8 datevu8 weekvu8 hourvu8 minvu8 sec)

可見這種方法來傳遞參數非常繁瑣。所以以結構體作為函數的入口參數的好處之一就是函數的聲void DsipDateTime(_calendar_obj DateTimeVal)不需要改變,只需要增加結構體的成員變量,然後在函數的內部實現上calendar.week作相應的處理即可。這樣,在程序的修改、維護方面作用顯著。

typedef struct //公曆日期和時間結構體

{

vu16 year;

vu8 month;

vu8 date;

vu8 week;

vu8 hour;

vu8 min;

vu8 sec;

}_calendar_obj;

_calendar_obj calendar; //定義結構體變量

(3)結構體的內存對齊原則可以提CPU對內存的訪問速(以空間換取時)

並且,結構體成員變量的地址可以根據基地(以偏移offset)計算。我們先來看看下面的一段簡單的程序,對於此程序的分析會在2部分結構體成員變量內存對齊中詳細說明。

#include

int main()

{

struct //聲明結構char_short_long

{

char c;

short s;

long l;

}char_short_long;

struct //聲明結構long_short_char

{

long l;

short s;

char c;

}long_short_char;

struct //聲明結構char_long_short

{

char c;

long l;

short s;

}char_long_short;

printf(" \n");

printf(" Size of char = %d bytes\n",sizeof(char));

printf(" Size of shrot = %d bytes\n",sizeof(short));

printf(" Size of long = %d bytes\n",sizeof(long));

printf(" \n"); //char_short_long

printf(" Size of char_short_long = %d bytes\n",sizeof(char_short_long));

printf(" Addr of char_short_long.c = 0x%p (10進位%d)\n",&char_short_long.c,&char_short_long.c);

printf(" Addr of char_short_long.s = 0x%p (10進位%d)\n",&char_short_long.s,&char_short_long.s);

printf(" Addr of char_short_long.l = 0x%p (10進位%d)\n",&char_short_long.l,&char_short_long.l);

printf(" \n");

printf(" \n"); //long_short_char

printf(" Size of long_short_char = %d bytes\n",sizeof(long_short_char));

printf(" Addr of long_short_char.l = 0x%p (10進位%d)\n",&long_short_char.l,&long_short_char.l);

printf(" Addr of long_short_char.s = 0x%p (10進位%d)\n",&long_short_char.s,&long_short_char.s);

printf(" Addr of long_short_char.c = 0x%p (10進位%d)\n",&long_short_char.c,&long_short_char.c);

printf(" \n");

printf(" \n"); //char_long_short

printf(" Size of char_long_short = %d bytes\n",sizeof(char_long_short));

printf(" Addr of char_long_short.c = 0x%p (10進位%d)\n",&char_long_short.c,&char_long_short.c);

printf(" Addr of char_long_short.l = 0x%p (10進位%d)\n",&char_long_short.l,&char_long_short.l);

printf(" Addr of char_long_short.s = 0x%p (10進位%d)\n",&char_long_short.s,&char_long_short.s);

printf(" \n");

return 0;

}

程序的運行結果如(注意:括號內的數據是成員變量的地址的十進位形)

2.結構體成員變量內存對齊

首先,我們來分析一下上面程序的運行結果。前三行說明在我的程序中char1個位元組short2個位元組long4個位元組char_short_longlong_short_charchar_long_short是三個結構體成員相同但是成員變量的排列順序不同。並且從程序的運行結果來看,

Size of char_short_long = 8 bytes

Size of long_short_char = 8 bytes

Size of char_long_short = 12 bytes //比前兩種情況4 byte

並且,還要注意到1 byte (char)+ 2 byte (short)+ 4 byte (long) = 7 byte,而不8 byte

所以,結構體成員變量的放置順序影響著結構體所占的內存空間的大小。一個結構體變量所占內存的大小不一定等於其成員變量所占空間之和。如果一個用戶程序或者操作系(uC/OS-II)中存在大量結構體變量時,這種內存占用必須要進行優化,也就是說,結構體內部成員變量的排列次序是有講究的。

結構體成員變量到底是如何存放的呢?

在這裡,我就不賣關子了,直接給出如下結論,在沒#pragma pack宏的情況下:

·1結構struct或聯union)的數據成員,第一個數據成員放offset0的地方,以後每個數據成員存儲的起始位置要從該成員大小的整數倍開始(比int32位機4位元組,則要4的整數倍地址開始存儲)。

·2結構體的總大小,也就sizeof的結果,必須是其內部最大成員的整數倍,不足的要補齊。

·3結構體作為成員時,結構體成員要從其內部最大元素大小的整數倍地址開始存儲。struct a里存struct bbcharintdouble等元素時,那b應該8的整數倍地址處開始存儲,因sizeof(double) = 8 bytes

這裡,我們結合上面的程序來分(暫時不討論原3)

先看char_short_longlong_short_char這兩個結構體,從它們的成員變量的地址可以看出來,這兩個結構體符合原1和原2。注意, char_short_long的成員變量的地址中char_short_long.s的地址1244994,也就是說1244993,只是了!

成員變量

成員變量十六進位地址

成員變量十進位地址

char_long_short.c

0x0012FF2C

1244972

char_long_short.l

0x0012FF30

1244976

char_long_short.s

0x0012FF34

1244980

可見,其內存分布圖如下,12 bytes

首先12449721整除,所char_long_short.c1244972處沒有問(其實,char型成員變量自身來說,其放在任何地址單元處都沒有問),依據原1,在之後1244973~1244975中都沒有能4(sizeof(long)=4bytes)整除的12449764整除,所char_long_short.l應該放1244976處,那麼同理,最後一.s(sizeof(short)=2 bytes)是應該放1244980處。

是不是這樣就完畢了?不是,還有原2。依據原2的要求char_long_short這個構造體所占的空間大小應該是其占內存空間最大的成員變量的大小的整數倍。假如我們到此就完畢了,那char_long_short所占的內存空間1244972~124498110bytes,不合乎原2,所以,必需在最後補2 bytes(1244982~1244983)

至此,一個構造體的內存布局完成了。

下面我們依照上述原則,來驗證這樣的分析是不是正確。按上面的分析,地址單12449731244974124497512449821244983都是空(char_long_short未用到,只是占位)。假如我們的分析是正確的,那麼,定義這樣一個構造體,其所占內存也應該12 bytes

struct //聲明構造char_long_short_new

{

char c;

char add1; //補齊空間

char add2; //補齊空間

char add3; //補齊空間

long l;

short s;

char add4; //補齊空間

char add5; //補齊空間

}char_long_short_new;

可見,我們的分析是正確的。至於原3,大家能夠自己編程驗證,這裡就不再探討了。

所以,沒論你是VC6.0Keil C51,還Keil MDK中,當你須要定義一個構造體時,只有你稍微留心構造體成員變量內存對齊這一現象,就能夠在很大程度上節MCURAM。這一點不僅僅應用於實際編程,在很多大型公司,假IBM、微軟、百度、華為的筆試和面試中,也是常見的。

這三大塊硬骨頭是進C語言的絆腳石,下功夫拿掉根本C語言的大動脈就打通了,那麼再去進修別的內容就相比照較簡略了。編程進修過程中越是痛苦的時候,學到的東西就會越多,克服過去就會自己的技能,放棄了前面的付出的時長都將清零。越是難學的語言在入門之後,在入門之後越覺得過癮,而且還容易上癮。你上癮了沒?還是放棄了?

文章來源: https://twgreatdaily.com/zh-hk/f1583cca7cdde055dddd2457416e6103.html