日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

程序的不同段的区别和作用

發布時間:2024/4/19 编程问答 24 豆豆
生活随笔 收集整理的這篇文章主要介紹了 程序的不同段的区别和作用 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

本文轉載自 : 鏈接

一.

在學習之前我們先看看ELF文件。


ELF分為三種類型:.o 可重定位文件(relocalble file),可執行文件以及共享庫(shared library),三種格式基本上從結構上是一樣的,只是具體到每一個結構不同。下面我們就從整體上看看這3種格式從文件內容上存儲的方式,spec上有張圖是比較經典的:如上圖:
?其實從文件存儲的格式來說,上面的兩種view實際上是一樣的,Segment實際上就是由section組成的,將相應的一些section映射到一起就叫segment了,就是說segment是由0個或多個section組成的,實際上本質都是section。在這里我們首先來仔細了解一下section和segment的概念:section就是相同或者相似信息的集合,比如我們比較熟悉的.text .data ?.bss section,.text是可執行指令的集合,.data是初始化后數據的集合,.bss是未初始化數據的集合。實際上我們也可以將一個程序的所有內容都放在一起,就像dos一樣,但是將可執行程序分成多個section是很有好處的,比如說我們可以將.text section放在memory的只讀空間內,將可變的.data section放在memory的可寫空間內。

從可執行文件的角度來講,如果一個數據未被初始化那就不需要為其分配空間,所以.data和.bss一個重要的區別就是.bss并不占用可執行文件的大小,它只是記載需要多少空間來存儲這些未初始化數據,而不分配實際的空間。

可以通過命令?$ readelf -l a.out 查看文件的格式和組成。

二.

站在匯編語言的角度,一個程序分為:
數據段 -- DS
堆棧段 -- SS
代碼段 -- CS
擴展段 -- ES

站在高級語言的角度,根據APUE,一個程序分為如下段:
text
data (initialized)
bss
stack
heap

? ? ? ? 1.一般情況下,一個可執行二進制程序(更確切的說,在Linux操作系統下為一個進程單元,在UC/OSII中被稱為任務)在存儲(沒有調入到內存運行)時擁有3個部分,分別是代碼段(text)、數據段(data)和BSS段。這3個部分一起組成了該可執行程序的文件

可執行二進制程序 =?代碼段(text)+數據段(data)+BSS段

? ? ? ? 2.而當程序被加載到內存單元時,則需要另外兩個域:堆域和棧域。圖1-1所示為可執行代碼存儲態運行態的結構對照圖。一個正在運行的C程序占用的內存區域分為代碼段、初始化數據段、未初始化數據段(BSS)、堆、棧5個部分。

正在運行的C程序?=?代碼段+初始化數據段(data)+未初始化數據段(BSS)+堆+棧

? ? ? ?3.在將應用程序加載到內存空間執行時,操作系統負責代碼段、數據段和BSS段的加載,并將在內存中為這些段分配空間。棧亦由操作系統分配和管理,而不需要程序員顯示地管理;堆段由程序員自己管理,即顯示地申請和釋放空間。

? ? ? ? ? 4.動態分配與靜態分配,二者最大的區別在于:1.?直到Run-Time的時候,執行動態分配,而在compile-time的時候,就已經決定好了分配多少Text+Data+BSS+Stack2.通過malloc()動態分配的內存,需要程序員手工調用free()釋放內存,否則容易導致內存泄露,而靜態分配的內存則在進程執行結束后系統釋放(Text, Data),?Stack段中的數據很短暫,函數退出立即被銷毀。



?圖1-1從可執行文件a.out的角度來講,如果一個數據未被初始化那就不需要為其分配空間,所以.data和.bss一個重要的區別就是.bss并不占用可執行文件的大小,它只是記載需要多少空間來存儲這些未初始化數據,而不分配實際的空間

三.

代碼段 --textcode segment/text segment)
text段在內存中被映射為只讀,但.data和.bss是可寫的。
text段是程序代碼段,在AT91庫中是表示程序段的大小,它是由編譯器在編譯連接時自動計算的,當你在鏈接定位文件中將該符號放置在代碼段后,那么該符號表示的值就是代碼段大小,編譯連接時,該符號所代表的值會自動代入到源程序中。

數據段 -- data
data包含靜態初始化的數據,所以有初值的全局變量和static變量在data區。段的起始位置也是由連接定位文件所確定,大小在編譯連接時自動分配,它和你的程序大小沒有關系,但和程序使用到的全局變量,常量數量相關。數據段屬于靜態內存分配。?

bss段--bss
bss是英文Block Started by Symbol的簡稱,通常是指用來存放程序中未初始化的全局變量的一塊內存區域,在程序載入時由內核清0。BSS段屬于靜態內存分配。它的初始值也是由用戶自己定義的連接定位文件所確定,用戶應該將它定義在可讀寫的RAM區內,源程序中使用malloc分配的內存就是這一塊,它不是根據data大小確定,主要由程序中同時分配內存最大值所確定,不過如果超出了范圍,也就是分配失敗,可以等空間釋放之后再分配。BSS段屬于靜態內存分配。

stack:
棧(stack)保存函數的局部變量(但不包括static聲明的變量,?static?意味著?在數據段中?存放變量),參數以及返回值。是一種“后進先出”(Last In First Out,LIFO)的數據結構,這意味著最后放到棧上的數據,將會是第一個從棧上移走的數據。對于哪些暫時存貯的信息,和不需要長時間保存的信息來說,LIFO這種數據結構非常理想。在調用函數或過程后,系統通常會清除棧上保存的局部變量、函數調用信息及其它的信息。棧另外一個重要的特征是,它的地址空間“向下減少”,即當棧上保存的數據越多,棧的地址就越低。棧(stack)的頂部在可讀寫的RAM區的最后。

heap:
堆(heap)保存函數內部動態分配內存,是另外一種用來保存程序信息的數據結構,更準確的說是保存程序的動態變量。堆是“先進先出”(First In first Out,FIFO)數據結構。它只允許在堆的一端插入數據,在另一端移走數據。堆的地址空間“向上增加”,即當堆上保存的數據越多,堆的地址就越高。

下圖是APUE中的一個典型C內存空間分布圖:


所以可以知道傳入的參數,局部變量,都是在棧頂分布,隨著子函數的增多而向下增長.
函數的調用地址(函數運行代碼),全局變量,靜態變量都是在分配內存的低部存在,而malloc分配的堆則存在于這些內存之上,并向上生長.

舉例1:

[c] view plain copy print?
  • #include?<stdio?h="">??
  • const?int????g_A???????=?10;?????????//代碼段??
  • int??????????g_B???????=?20;?????????//數據段??
  • static?int???g_C???????=?30;?????????//數據段??
  • static?int???g_D;????????????????????//BSS段??
  • int??????????g_E;????????????????????//BSS段??
  • char????????*p1;?????????????????????//BSS段??
  • ??
  • void?main(?)??
  • {??
  • ????int???????????local_A;????????????//棧??
  • ????int???????????local_B;????????????//棧??
  • ????static?int????local_C?=?0;????????//數據段??
  • ????static?int????local_D;????????????//數據段??
  • ??????
  • ????char????????*p3?=?"123456";?????//123456在代碼段,p3在棧上??
  • ??
  • ????p1?=?(char?*)malloc(?10?);??????//堆,分配得來得10字節的區域在堆區??
  • ????strcpy(?p1,?"123456"?);?????????//123456{post.content}放在常量區,編譯器可能會將它與p3所指向?的"123456"優化成一塊??
  • ??
  • ????printf("hight?address\n");??
  • ????printf("-------------棧--------------\n");??
  • ????printf(?"棧,????局部變量,???????????????????????????local_A,?addr:0x%08x\n",?&local_A?);??
  • ????printf(?"棧,????局部變量,(后進棧地址相對local_A低)??local_B,?addr:0x%08x\n",?&local_B?);??
  • ????printf("-------------堆--------------\n");??
  • ????printf(?"堆,????malloc分配內存,?????????????p1,??????addr:0x%08x\n",?p1?);??
  • ????printf("------------BSS段------------\n");??
  • ????printf(?"BSS段,?全局變量,???????未初始化????g_E,?????addr:0x%08x\n",?&g_E,?g_E?);??????
  • ????printf(?"BSS段,?靜態全局變量,???未初始化,???g_D,?????addr:0x%08x\n",?&g_D?);??
  • ????printf(?"BSS段,?靜態局部變量,???初始化,?????local_C,?addr:0x%08x\n",?&local_C);??
  • ????printf(?"BSS段,?靜態局部變量,???未初始化,???local_D,?addr:0x%08x\n",?&local_D);??
  • ????printf("-----------數據段------------\n");??
  • ????printf(?"數據段,全局變量,???????初始化??????g_B,?????addr:0x%08x\n",?&g_B);??
  • ????printf(?"數據段,靜態全局變量,???初始化,?????g_C,?????addr:0x%08x\n",?&g_C);??
  • ????printf("-----------代碼段------------\n");??
  • ????printf(?"代碼段,全局初始化變量,?只讀const,??g_A,?????addr:0x%08x\n\n",?&g_A);??
  • ????printf("low?address\n");??
  • ????return;??
  • }??
  • </stdio>??
  • #include <stdio h=""> const int g_A = 10; //代碼段 int g_B = 20; //數據段 static int g_C = 30; //數據段 static int g_D; //BSS段 int g_E; //BSS段 char *p1; //BSS段void main( ) {int local_A; //棧int local_B; //棧static int local_C = 0; //數據段static int local_D; //數據段char *p3 = "123456"; //123456在代碼段,p3在棧上p1 = (char *)malloc( 10 ); //堆,分配得來得10字節的區域在堆區strcpy( p1, "123456" ); //123456{post.content}放在常量區,編譯器可能會將它與p3所指向 的"123456"優化成一塊printf("hight address\n");printf("-------------棧--------------\n");printf( "棧, 局部變量, local_A, addr:0x%08x\n", &local_A );printf( "棧, 局部變量,(后進棧地址相對local_A低) local_B, addr:0x%08x\n", &local_B );printf("-------------堆--------------\n");printf( "堆, malloc分配內存, p1, addr:0x%08x\n", p1 );printf("------------BSS段------------\n");printf( "BSS段, 全局變量, 未初始化 g_E, addr:0x%08x\n", &g_E, g_E ); printf( "BSS段, 靜態全局變量, 未初始化, g_D, addr:0x%08x\n", &g_D );printf( "BSS段, 靜態局部變量, 初始化, local_C, addr:0x%08x\n", &local_C);printf( "BSS段, 靜態局部變量, 未初始化, local_D, addr:0x%08x\n", &local_D);printf("-----------數據段------------\n");printf( "數據段,全局變量, 初始化 g_B, addr:0x%08x\n", &g_B);printf( "數據段,靜態全局變量, 初始化, g_C, addr:0x%08x\n", &g_C);printf("-----------代碼段------------\n");printf( "代碼段,全局初始化變量, 只讀const, g_A, addr:0x%08x\n\n", &g_A);printf("low address\n");return; } </stdio>

    運行結果:

    [c] view plain copy print?
  • hight?address??
  • -------------棧--------------??
  • 棧,????局部變量,???????????????????????????local_A,?addr:0xffa70c1c??
  • 棧,????局部變量,(后進棧地址相對local_A低)??local_B,?addr:0xffa70c18??
  • -------------堆--------------??
  • 堆,????malloc分配內存,?????????????p1,??????addr:0x087fe008??
  • ------------BSS段------------??
  • BSS段,?全局變量,???????未初始化????g_E,?????addr:0x08049a64??
  • BSS段,?靜態全局變量,???未初始化,???g_D,?????addr:0x08049a5c??
  • BSS段,?靜態局部變量,???初始化,?????local_C,?addr:0x08049a58??
  • BSS段,?靜態局部變量,???未初始化,???local_D,?addr:0x08049a54??
  • -----------數據段------------??
  • 數據段,全局變量,???????初始化??????g_B,?????addr:0x08049a44??
  • 數據段,靜態全局變量,???初始化,?????g_C,?????addr:0x08049a48??
  • -----------代碼段------------??
  • 代碼段,全局初始化變量,?只讀const,??g_A,?????addr:0x08048620??
  • ??
  • low?address??
  • hight address -------------棧-------------- 棧, 局部變量, local_A, addr:0xffa70c1c 棧, 局部變量,(后進棧地址相對local_A低) local_B, addr:0xffa70c18 -------------堆-------------- 堆, malloc分配內存, p1, addr:0x087fe008 ------------BSS段------------ BSS段, 全局變量, 未初始化 g_E, addr:0x08049a64 BSS段, 靜態全局變量, 未初始化, g_D, addr:0x08049a5c BSS段, 靜態局部變量, 初始化, local_C, addr:0x08049a58 BSS段, 靜態局部變量, 未初始化, local_D, addr:0x08049a54 -----------數據段------------ 數據段,全局變量, 初始化 g_B, addr:0x08049a44 數據段,靜態全局變量, 初始化, g_C, addr:0x08049a48 -----------代碼段------------ 代碼段,全局初始化變量, 只讀const, g_A, addr:0x08048620low address

    注意:
    編譯時需要-g選項,這樣才可以看elf信息;
    readelf -a a.out?

    查看這個執行文件的elf信息,摘錄部分如下:重點注意其中data段,text段還要有bss段的地址,然后比較這個地址和上面的運行結果,是否是在elf文件的各個段的地址之內。

    [c] view plain copy print?
  • Section?Headers:??
  • ??[Nr]?Name??????????????Type????????????Addr?????Off????Size???ES?Flg?Lk?Inf?Al??
  • ??[?0]???????????????????NULL????????????00000000?000000?000000?00??????0???0??0??
  • ??[?1]?.interp???????????PROGBITS????????08048114?000114?000013?00???A??0???0??1??
  • ??[?2]?.note.ABI-tag?????NOTE????????????08048128?000128?000020?00???A??0???0??4??
  • ??[?3]?.gnu.hash?????????GNU_HASH????????08048148?000148?000020?04???A??4???0??4??
  • ??[?4]?.dynsym???????????DYNSYM??????????08048168?000168?000070?10???A??5???1??4??
  • ??[?5]?.dynstr???????????STRTAB??????????080481d8?0001d8?000058?00???A??0???0??1??
  • ??[?6]?.gnu.version??????VERSYM??????????08048230?000230?00000e?02???A??4???0??2??
  • ??[?7]?.gnu.version_r????VERNEED?????????08048240?000240?000020?00???A??5???1??4??
  • ??[?8]?.rel.dyn??????????REL?????????????08048260?000260?000008?08???A??4???0??4??
  • ??[?9]?.rel.plt??????????REL?????????????08048268?000268?000028?08???A??4??11??4??
  • ??[10]?.init?????????????PROGBITS????????08048290?000290?000017?00??AX??0???0??4??
  • ??[11]?.plt??????????????PROGBITS????????080482a8?0002a8?000060?04??AX??0???0??4??
  • ??[12]?.text?????????????PROGBITS????????08048310?000310?0002e8?00??AX??0???0?16??
  • ??[13]?.fini?????????????PROGBITS????????080485f8?0005f8?00001c?00??AX??0???0??4??
  • ??[14]?.rodata???????????PROGBITS????????08048614?000614?000326?00???A??0???0??4??
  • ??[15]?.eh_frame?????????PROGBITS????????0804893c?00093c?000004?00???A??0???0??4??
  • ??[16]?.ctors????????????PROGBITS????????08049940?000940?000008?00??WA??0???0??4??
  • ??[17]?.dtors????????????PROGBITS????????08049948?000948?000008?00??WA??0???0??4??
  • ??[18]?.jcr??????????????PROGBITS????????08049950?000950?000004?00??WA??0???0??4??
  • ??[19]?.dynamic??????????DYNAMIC?????????08049954?000954?0000c8?08??WA??5???0??4??
  • ??[20]?.got??????????????PROGBITS????????08049a1c?000a1c?000004?04??WA??0???0??4??
  • ??[21]?.got.plt??????????PROGBITS????????08049a20?000a20?000020?04??WA??0???0??4??
  • ??[22]?.data?????????????PROGBITS????????08049a40?000a40?00000c?00??WA??0???0??4??
  • ??[23]?.bss??????????????NOBITS??????????08049a4c?000a4c?00001c?00??WA??0???0??4??
  • ??[24]?.comment??????????PROGBITS????????00000000?000a4c?000114?00??????0???0??1??
  • ??[25]?.debug_aranges????PROGBITS????????00000000?000b60?000020?00??????0???0??1??
  • ??[26]?.debug_pubnames???PROGBITS????????00000000?000b80?00003a?00??????0???0??1??
  • ??[27]?.debug_info???????PROGBITS????????00000000?000bba?0001f4?00??????0???0??1??
  • ??[28]?.debug_abbrev?????PROGBITS????????00000000?000dae?00006f?00??????0???0??1??
  • ??[29]?.debug_line???????PROGBITS????????00000000?000e1d?000058?00??????0???0??1??
  • ??[30]?.debug_frame??????PROGBITS????????00000000?000e78?00003c?00??????0???0??4??
  • ??[31]?.debug_str????????PROGBITS????????00000000?000eb4?00000d?00??????0???0??1??
  • ??[32]?.debug_loc????????PROGBITS????????00000000?000ec1?000043?00??????0???0??1??
  • ??[33]?.shstrtab?????????STRTAB??????????00000000?000f04?000143?00??????0???0??1??
  • ??[34]?.symtab???????????SYMTAB??????????00000000?0015e8?000560?10?????35??60??4??
  • ??[35]?.strtab???????????STRTAB??????????00000000?001b48?0002ad?00??????0???0??1??
  • Key?to?Flags:??
  • ??W?(write),?A?(alloc),?X?(execute),?M?(merge),?S?(strings)??
  • ??I?(info),?L?(link?order),?G?(group),?x?(unknown)??
  • ??O?(extra?OS?processing?required)?o?(OS?specific),?p?(processor?specific)??
  • Section Headers:[Nr] Name Type Addr Off Size ES Flg Lk Inf Al[ 0] NULL 00000000 000000 000000 00 0 0 0[ 1] .interp PROGBITS 08048114 000114 000013 00 A 0 0 1[ 2] .note.ABI-tag NOTE 08048128 000128 000020 00 A 0 0 4[ 3] .gnu.hash GNU_HASH 08048148 000148 000020 04 A 4 0 4[ 4] .dynsym DYNSYM 08048168 000168 000070 10 A 5 1 4[ 5] .dynstr STRTAB 080481d8 0001d8 000058 00 A 0 0 1[ 6] .gnu.version VERSYM 08048230 000230 00000e 02 A 4 0 2[ 7] .gnu.version_r VERNEED 08048240 000240 000020 00 A 5 1 4[ 8] .rel.dyn REL 08048260 000260 000008 08 A 4 0 4[ 9] .rel.plt REL 08048268 000268 000028 08 A 4 11 4[10] .init PROGBITS 08048290 000290 000017 00 AX 0 0 4[11] .plt PROGBITS 080482a8 0002a8 000060 04 AX 0 0 4[12] .text PROGBITS 08048310 000310 0002e8 00 AX 0 0 16[13] .fini PROGBITS 080485f8 0005f8 00001c 00 AX 0 0 4[14] .rodata PROGBITS 08048614 000614 000326 00 A 0 0 4[15] .eh_frame PROGBITS 0804893c 00093c 000004 00 A 0 0 4[16] .ctors PROGBITS 08049940 000940 000008 00 WA 0 0 4[17] .dtors PROGBITS 08049948 000948 000008 00 WA 0 0 4[18] .jcr PROGBITS 08049950 000950 000004 00 WA 0 0 4[19] .dynamic DYNAMIC 08049954 000954 0000c8 08 WA 5 0 4[20] .got PROGBITS 08049a1c 000a1c 000004 04 WA 0 0 4[21] .got.plt PROGBITS 08049a20 000a20 000020 04 WA 0 0 4[22] .data PROGBITS 08049a40 000a40 00000c 00 WA 0 0 4[23] .bss NOBITS 08049a4c 000a4c 00001c 00 WA 0 0 4[24] .comment PROGBITS 00000000 000a4c 000114 00 0 0 1[25] .debug_aranges PROGBITS 00000000 000b60 000020 00 0 0 1[26] .debug_pubnames PROGBITS 00000000 000b80 00003a 00 0 0 1[27] .debug_info PROGBITS 00000000 000bba 0001f4 00 0 0 1[28] .debug_abbrev PROGBITS 00000000 000dae 00006f 00 0 0 1[29] .debug_line PROGBITS 00000000 000e1d 000058 00 0 0 1[30] .debug_frame PROGBITS 00000000 000e78 00003c 00 0 0 4[31] .debug_str PROGBITS 00000000 000eb4 00000d 00 0 0 1[32] .debug_loc PROGBITS 00000000 000ec1 000043 00 0 0 1[33] .shstrtab STRTAB 00000000 000f04 000143 00 0 0 1[34] .symtab SYMTAB 00000000 0015e8 000560 10 35 60 4[35] .strtab STRTAB 00000000 001b48 0002ad 00 0 0 1 Key to Flags:W (write), A (alloc), X (execute), M (merge), S (strings)I (info), L (link order), G (group), x (unknown)O (extra OS processing required) o (OS specific), p (processor specific)

    注意靜態變量初始化為零全局靜態變量初始化為零的情況,都是存儲在bss段★★★

    從上面的elf文件可以看出,

    ? [23] .bss ? ? ? ? ? ? ?NOBITS ? ? ? ? ?08049a4c?000a4c 00001c 00 ?WA ?0 ? 0 ?4

    ? [22] .data ? ? ? ? ? ? PROGBITS ? ?08049a40000a40 00000c 00 ?WA ?0 ? 0 ?4

    ? [12] .text ? ? ? ? ? ? PROGBITS ? ? ?08048310000310 0002e8 00 ?AX ?0 ? 0 16

    但是在結果中顯示:?BSS段, 靜態局部變量, ? 初始化, ? ?local_C, addr:0x08049a58?

    ?(0x08049a58 大于0x08049a4c 屬于bss段)是初始化的靜態局部變量但是卻屬于bss段,為什么?

    原因是:local_C是局部靜態變量但是卻初始化為零。這和沒有初始化,默認是零的情況一樣,都存儲在bss段,如果初始化為其他的值,那么local_C這個變量就會存儲在data段。

    可執行文件大小由什么決定?

    可執行文件在存儲時分為代碼段、數據段和BSS段三個部分。

    【例一】
    程序1:
    int ar[30000];
    void main()
    {
    ? ? ......
    }?
    程序2:
    int ar[300000] =??{1, 2, 3, 4, 5, 6 };
    void main()
    {
    ? ? ......
    }?
    發現程序2編譯之后所得的.exe文件比程序1的要大得多。當下甚為不解,于是手工編譯了一下,并使用了/FAs編譯選項來查看了一下其各自的.asm,發現在程序1.asm中ar的定義如下:
    _BSS SEGMENT
    ? ????ar@@3PAHA DD 0493e0H DUP (?)? ? ; ar
    _BSS ENDS?
    而在程序2.asm中,ar被定義為:
    _DATA SEGMENT
    ? ????ar@@3PAHA DD 01H? ???; ar
    ? ?? ?? ?? ?? ? DD 02H
    ? ?? ?? ?? ?? ? DD 03H
    ? ?? ?? ?? ?? ? ORG $+1199988
    _DATA ENDS?
    區別很明顯,一個位于.bss段,而另一個位于.data段,兩者的區別在于:全局的未初始化變量存在于.bss段中,具體體現為一個占位符;全局的已初始化變量存于.data段中;而函數內的自動變量都在棧上分配空間。

    .bss是不占用.exe文件空間的,其內容由操作系統初始化(清零);而.data卻需要占用,其內容由程序初始化,因此造成了上述情況。


    以上僅僅做為學習只用!!

    參考材料: 可執行文件(ELF)格式的理解
    http://www.cnblogs.com/xmphoenix/archive/2011/10/23/2221879.html C程序內存區域分配(5個段作用)
    http://www.cnblogs.com/bigbigtree/archive/2012/11/23/2784137.html

    總結

    以上是生活随笔為你收集整理的程序的不同段的区别和作用的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。