你所不知道的C語言:指標篇 - HackMD
文章推薦指數: 80 %
The construction of a pointer type from a referenced type is called ''pointer type derivation''. 注意到術語!這是C 語言只有call-by-value 的實證,函式的傳遞都 ...
ownedthisnote
Published
LinkedwithGitHub
Like33
Bookmark
Subscribe
Edit
---
tags:DYKC,C,CLANG,CLANGUAGE,pointer
---
#[你所不知道的C語言](https://hackmd.io/@sysprog/c-prog/):指標篇
*「指標」扮演「記憶體」和「物件」之間的橋樑*
Copyright(**慣C**)2015,2016,2018[宅色夫](http://wiki.csie.ncku.edu.tw/User/jserv)
==[直播錄影(上)](https://youtu.be/G7vERppua9o)==
==[直播錄影(下)](https://www.youtube.com/watch?v=Owxols1RTAg&feature=youtu.be)==
&A"]
structptr[label=" &B"]
structptr[label=" &ptrA"]
structadptr[label="&ptrA(temp)| &ptrA"]
structadptr[label="&ptrA(temp)|
##這個講座並非「頭腦體操」
*stackoverflow上的[頭腦體操](http://stackoverflow.com/questions/8208021/how-to-increment-a-pointer-address-and-pointers-value/8208106#8208106)
取自[CTrapsandPitfalls](http://www.literateprogramming.com/ctraps.pdf)的案例"UnderstandingDeclarations":
```cpp
(*(void(*)())0)();
```
可改寫為以下敘述:
```cpp
typedefvoid(*funcptr)();
(*(funcptr)0)();
```
-[][godbolt](http://gcc.godbolt.org/):直接在網頁上看到gcc生成的程式碼
```cpp
intmain(){
typedefvoid(*funcptr)();
(*(funcptr)(void*)0)();
}
```
對應的組合語言,搭配`-Os`(空間最佳化)
```assembly
main:
pushq%rax
xorl%eax,%eax
call*%rax
xorl%eax,%eax
popq%rdx
ret
```
KenThompson自貝爾實驗室退休後成為一名飛行員。
大概是整日翱翔天際,獲得頗多啟發,在2006年,他進入Google工作,隔年他和過去貝爾實驗室的同僚[RobPike](https://en.wikipedia.org/wiki/Rob_Pike)及RobertGriesemer等人在公司內部提出嶄新的Go程式語言,後者可用於雲端運算在內的眾多領域。
指標這個好東西,當然也要從C語言帶過去給Go語言,連同美妙的struct。
-根據第一份[GolangTalk](https://talks.golang.org/2009/go_talk-20091030.pdf),RobertGriesemer,KenThompson及RobPike等三人認為,世界在變,但系統語言卻已十年未有劇烈變革
-Go之前的程式語言未能達到:
-新增函式庫不是一個正確的方向
-需要重新思考整個架構來開發新的程式語言
在實做層面,pointer和struct往往是成雙成對存在(下方會解釋)
##先羅列你已經知道的部分
{%youtubet5NszbIerYc%}
>[DavidBrailsford](http://www.cs.nott.ac.uk/~psadb1/)教授解說C語言指標
*[C語言:超好懂的指標](https://kopu.chat/2017/05/15/c%E8%AA%9E%E8%A8%80-%E8%B6%85%E5%A5%BD%E6%87%82%E7%9A%84%E6%8C%87%E6%A8%99%EF%BC%8C%E5%88%9D%E5%AD%B8%E8%80%85%E8%AB%8B%E9%80%B2%EF%BD%9E/)
*[EverythingyouneedtoknowaboutpointersinC](https://boredzo.org/pointers/)
*疑惑
*該如何解釋[qsort(3)](https://linux.die.net/man/3/qsort)的參數和設計考量呢?
*為何我看到的程式碼往往寫成類似下面這樣?
```cpp
structlist**lpp;
for(lpp=&list;*lpp!=NULL;lpp=&(*lpp)->next)
```
##回頭看C語言規格書
在[開發工具和規格標準](https://hackmd.io/s/HJFyt37Mx)篇提過參考第一手資料的重要性,以下ISO/IEC9899(簡稱"C99")和指標相關的描述:
-[規格書](http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1256.pdf)(PDF)搜尋"***object***",共出現735處
-搜尋"***pointer***",共出現637處。
有趣的是,許多教材往往不談object,而是急著談論pointer,殊不知,這兩者其實就是一體兩面
-object!=object-oriented
-前者的重點在於「資料表達法」,後者的重點在於"everythingisobject"
-C11([ISO/IEC9899:201x](http://www.open-std.org/jtc1/sc22/WG14/www/docs/n1570.pdf))/[網頁版](http://port70.net/~nsz/c/c11/n1570.html)
-`&`不要都念成and,涉及指標操作的時候,要讀為"addressof"
-C99標準[6.5.3.2]Addressandindirectionoperators提到'==&=='address-ofoperator
-C99[3.14]***object***
-regionofdatastorageintheexecutionenvironment,thecontentsofwhichcanrepresentvalues
-在C語言的物件就指在執行時期,==資料==儲存的區域,可以明確表示數值的內容
-很多人誤認在C語言程式中,(int)7和(float)7.0是等價的,其實以資料表示的角度來看,這兩者截然不同,前者對應到二進位的"111",而後者以IEEE754表示則大異於"111"
-C99[6.2.4]***Storagedurationsofobjects***
-Anobjecthasastoragedurationthatdeterminesitslifetime.Therearethreestoragedurations:static,automatic,andallocated.
>注意生命週期(lifetime)的概念,中文講「初始化」時,感覺像是「盤古開天」,很容易令人誤解。
其實initialize的[英文意義](http://dictionary.reference.com/browse/initialize)很狹隘:"toset(variables,counters,switches,etc.)totheirstartingvaluesatthebeginningofaprogramorsubprogram."
-Thelifetimeofanobjectistheportionofprogramexecutionduringwhichstorageisguaranteedtobereservedforit.Anobjectexists,hasaconstantaddressandretainsitslast-storedvaluethroughoutitslifetime.Ifanobjectisreferredtooutsideofitslifetime,thebehaviorisundefined.
>在object的生命週期以內,其存在就意味著有對應的常數記憶體位址。
注意,C語言永遠只有call-by-value
-Thevalueofapointerbecomesindeterminatewhentheobjectitpointstoreachestheendofitslifetime.
>作為object操作的「代名詞」(alias)的pointer,倘若要在object生命週期以外的時機,去取出pointer所指向的object內含值,是未知的。
考慮先做`ptr=malloc(size);free(ptr);`倘若之後做`*ptr`,這個allocatedstorage已經超出原本的生命週期
-Anobjectwhoseidentifierisdeclaredwithnolinkageandwithoutthestorage-classspecifierstatichasautomaticstorageduration.
-C99[6.2.5]***Types***
-Apointertypemaybederivedfromafunctiontype,anobjecttype,oranincompletetype,calledthereferencedtype.Apointertypedescribesanobjectwhosevalueprovidesareferencetoanentityofthereferencedtype.ApointertypederivedfromthereferencedtypeTissometimescalled‘‘pointertoT’’.Theconstructionofapointertypefromareferencedtypeiscalled‘‘pointertypederivation’’.
>注意到術語!這是C語言只有call-by-value的實證,函式的傳遞都涉及到數值
>這裡的"incompletetype"要注意看,稍後會解釋。
要區分`char[]`和`char*`
-Arithmetictypesandpointertypesarecollectivelycalledscalartypes.Arrayandstructuretypesarecollectivelycalledaggregatetypes.
>注意"scalartype"這個術語,日後我們看到`++`,`--`,`+=`,`-=`等操作,都是對scalar(純量)
>[[來源](http://www.cyut.edu.tw/~cpyu/oldphweb/chapter3/page3.htm)]純量只有大小,它們可用數目及單位來表示(例如溫度=30^o^C)。
純量遵守算數和普通的代數法則。
注意:純量有「單位」(可用`sizeof`操作子得知單位的「大小」),假設`ptr`是個pointertype,對`ptr++`來說,並不是單純`ptr=ptr+1`,而是遞增或遞移1個「單位」
-Anarraytypeofunknownsizeisanincompletetype.Itiscompleted,foranidentifierofthattype,byspecifyingthesizeinalaterdeclaration(withinternalorexternallinkage).Astructureoruniontypeofunknowncontentisanincompletetype.Itiscompleted,foralldeclarationsofthattype,bydeclaringthesamestructureoruniontagwithitsdefiningcontentlaterinthesamescope.
>這是C/C++常見的forwarddeclaration技巧的原理,比方說我們可以在標頭檔宣告`structGraphicsObject;`(不用給細部定義)然後`structGraphicsObject*initGraphics(intwidth,intheight);`是合法的,但`structGraphicsObjectobj;`不合法
-Array,function,andpointertypesarecollectivelycalledderiveddeclaratortypes.AdeclaratortypederivationfromatypeTistheconstructionofaderiveddeclaratortypefromTbytheapplicationofanarray-type,afunction-type,orapointer-typederivationtoT.
>這句話很重要,貌似三個不相關的術語「陣列」、「函式」,及「指標」都歸類為deriveddeclaratortypes,讀到此處會感到驚訝者,表示不夠理解C語言
一個函數在某一點的導數描述了這個函數在這一點附近的變化率。
導數的本質是通過極限的概念對函數進行局部的線性逼近。
函數在一點的導數等於它的圖像上這一點處之切線的斜率)
:notes:derivative的KK音標是[dəˋrɪvətɪv],而derivation的KK音標是[d,ɛrəv'eʃən]
:::
回到C語言,你看到一個數值,是scalar,但可能也是自某個型態衍生出的declaratortypederivation,實際對應到array,function,pointer等型態的derivation
**(練習題)**設定絕對地址為`0x67a9`的32-bit整數變數的值為`0xaa6`,該如何寫?
```cpp
*(int32_t*const)(0x67a9)=0xaa6;
/*Lvalue*/
```
-Apointertovoidshallhavethesamerepresentationandalignmentrequirementsasapointertoacharactertype.
>關鍵描述!規範`void*`和`char*`彼此可互換的表示法
```cpp
void*memcpy(void*dest,constvoid*src,size_tn);
```
##英文很重要
安裝`cdecl`程式,可以幫你產生C程式的宣告。
```shell
$sudoapt-getinstallcdecl
```
使用案例:
```shell
$cdecl
cdecl>declareaasarrayofpointertofunctionreturningpointertofunctionreturningpointertochar
```
會得到以下輸出:
```cpp
char*(*(*a[])())()
```
把前述C99規格的描述帶入,可得:
```shell
cdecl>declarearrayofpointertofunctionreturningstructtag
```
```cpp
structtag(*var[])()
```
如果你沒辦法用英文來解說C程式的宣告,通常表示你不理解!
`cdecl`可解釋C程式宣告的意義,比方說:
```shell
cdecl>explainchar*(*fptab[])(int)
declarefptabasarrayofpointertofunction(int)returningpointertochar
```
##`void*`之謎
-`void`在最早的C語言是不存在的,直到C89才確立,為何要設計這樣的型態呢?
-[最早的C語言中](https://www.bell-labs.com/usr/dmr/www/primevalC.html),任何函式若沒有特別標注返回型態,一律變成`int`(伴隨著`0`作為返回值),但這導致無從驗證functionprototype和實際使用的狀況
-`void*`的設計,導致開發者必須透過==explicit(顯式)==或強制轉型,才能存取最終的object,否則就會丟出編譯器的錯誤訊息,從而避免危險的指標操作
-我們無法直接對`void*`做數值操作
```cpp
void*p=...;
void*p2=p+1;/*whatexactlyisthesizeofvoid?*/
```
-換句話說,`void*`存在的目的就是為了強迫使用者使用==顯式轉型==或是==強制轉型==,以避免Undefinedbehavior產生
-C/C++[Implicitconversion](http://en.cppreference.com/w/cpp/language/implicit_conversion)vs.[Explicittypeconversion](https://en.cppreference.com/w/cpp/language/explicit_cast)
-C99對signextension的[定義和解說](https://www.ptt.cc/bbs/C_and_CPP/M.1460791524.A.603.html)
-對某硬體架構,像是ARM,我們需要額外的==alignment==。
ARMv5(含)以前,若要操作32-bit整數(uint32_t),該指標必須對齊32-bit邊界(否則會在dereference時觸發exception)。
於是,當要從`void*`位址讀取uint16_t時,需要這麼做:
```cpp
/*mayreceivewrongvalueifptrisnot2-bytealigned*/
/*portablewayofreadingalittle-endianvalue*/
uint16_tvalue=*(uint16_t*)ptr;
uint16_tvalue=*(uint8_t*)ptr|((*(uint8_t*)(ptr+1))<<8);
```
延伸閱讀:[記憶體管理、對齊及硬體特性](https://hackmd.io/@sysprog/c-memory)
###`void*`真的萬能嗎?
依據C99規格6.3.2.3:8[Pointers]
>Apointertoafunctionofonetypemaybeconvertedtoapointertoafunctionofanothertypeandbackagain;theresultshallcompareequaltotheoriginalpointer.Ifaconvertedpointerisusedtocallafunctionwhosetypeisnotcompatiblewiththepointed-totype,thebehaviorisundefined.
換言之,C99不保證pointerstodata(inthestandard,"objectsorincompletetypes"e.g.`char*`or`void*`)和pointerstofunctions之間相互轉換是正確的
-可能會招致undefinedbehavior(UB)
-注意:C99規範中,存在一系列的UB,詳見[未定義行為篇](https://hackmd.io/@sysprog/c-undefined-behavior)
>延伸閱讀:[TheLostArtofStructurePacking](http://www.catb.org/esr/structure-packing/)
##沒有「雙指標」只有「指標的指標」
「雙馬尾」(左右「獨立」的個體)和「馬尾的馬尾」(由單一個體關聯到另一個體的對應)不同
-中文的「[雙](https://www.moedict.tw/%E9%9B%99)」有「對稱」且「獨立」的意含,但這跟「指標的指標」行為完全迥異
-講「==雙==指標」已非「懂不懂C語言」,而是漢語認知的問題
C語言中,萬物皆是數值(everythingisavalue),函式呼叫當然只有call-by-value。
「指標的指標」(英文就是apointerofapointer)是個常見用來改變「傳入變數原始數值」的技巧。
考慮以下程式碼:
```cpp=1
intB=2;
voidfunc(int*p){p=&B;}
intmain(){
intA=1,C=3;
int*ptrA=&A;
func(ptrA);
printf("%d\n",*ptrA);
return0;
}
```
在第5行(含)之前的記憶體示意:
```graphviz
digraphstructs{
node[shape=record]
{rank=same;structa,structb,structc}
structptr[label="ptrA|
這不是我們期望的結果,該如何克服呢?可透過「指標的指標」來改寫,如下:
```cpp=1
intB=2;
voidfunc(int**p){*p=&B;}
intmain(){
intA=1,C=3;
int*ptrA=&A;
func(&ptrA);
printf("%d\n",*ptrA);
return0;
}
```
在第5行(含)之前的記憶體示意:
```graphviz
digraphstructs{
node[shape=record]
{rank=same;structa,structb,structc}
structptr[label="ptrA|
延伸閱讀:
*[CLinkedListImplementationPointervs.Pointer-to-PointerConsistency](https://softwareengineering.stackexchange.com/questions/260411/c-linked-list-implementation-pointer-vs-pointer-to-pointer-consistency)
*[你所不知道的C語言:linkedlist和非連續記憶體操作](https://hackmd.io/s/SkE33UTHf)
##forwarddeclaration搭配指標的技巧
案例:[oltk](https://github.com/openmoko/system-test-suite/tree/master/gta02-dm2/src/oltk)是[Openmoko](https://en.wikipedia.org/wiki/Openmoko)為了工廠測試而開發出的精簡繪圖系統,支援觸碰螢幕,程式碼不到1000行C語言程式。
執行畫面:(oltk的開發者是[olv](https://github.com/olvaffe))
![](https://hackpad-attachments.s3.amazonaws.com/embedded2015.hackpad.com_s0rlzR8wVtm_p.299401_1448120302988_oltk-dm2.png)
-[][oltk.h](https://github.com/openmoko/system-test-suite/blob/master/gta02-dm2/src/oltk/oltk.h)
```cpp
structoltk;//宣告(incompletetype,void)
structoltk_button;
typedefvoidoltk_button_cb_click(structoltk_button*button,void*data);
typedefvoidoltk_button_cb_draw(structoltk_button*button,
structoltk_rectangle*rect,void*data);
structoltk_button*oltk_button_add(structoltk*oltk,
intx,inty,
intwidth,intheight);
```
`structoltk`和`structoltk_button`沒有具體的定義(definition)或實做(implementation),僅有宣告(declaration)。
-[][oltk.c](https://github.com/openmoko/system-test-suite/blob/master/gta02-dm2/src/oltk/oltk.c)
```cpp
structoltk{
structgr*gr;
structoltk_button**zbuttons;
...
structoltk_rectangleinvalid_rect;
};
```
軟體界面(interface)揭露於oltk.h,不管structoltk內容怎麼修改,已公開的函式如`oltk_button_add`都是透過pointer存取給定的位址,而不用顧慮具體structoltk的實做,如此一來,不僅可以隱藏實作細節,還能兼顧二進位的相容性(binarycompatibility)。
同理,structoltk_button不管怎麼變更,在structoltk裡面也是用給定的pointer去存取,保留未來實做的彈性。
延伸閱讀
*[你所不知道的C語言:物件導向程式設計篇](https://hackmd.io/@sysprog/c-oop)
*[如何在指標中隱藏資料?](https://itw01.com/5OSE3G9.html)/[原文](https://arjunsreedharan.org/post/105266490272/hide-data-inside-pointers)
##Pointersvs.Arrays
-arrayvs.pointer
-indeclaration
-extern,如`externcharx[];`$\to$不能變更為pointer的形式
-definition/statement,如`charx[10]`$\to$不能變更為pointer的形式
-parameteroffunction,如`func(charx[])`$\to$可變更為pointer的形式$\to$`func(char*x)`
-inexpression
-array與pointer可互換
```cpp
intmain(){
intx[10]={0,1,2,3,4,5,6,7,8,9};
printf("%d%d%d%d\n",x[4],*(x+4),*(4+x),4[x]);
}
```
在[TheCProgrammingLanguage](http://www.amazon.com/The-Programming-Language-Brian-Kernighan/dp/0131103628)第2版,Page99寫道:
>Asformalparametersinafunctiondefinition,
該書Page100則寫:
>`chars[]`;and`char*s`areequivalent.
:::info
-這就是悲劇的由來,人們往往忘了前一頁
-`x[i]`總是被編譯器改寫為`*(x+i)`$\leftarrow$inexpression
:::
-C提供操作多維陣列的機制(C99[6.5.2.1]**_Arraysubscripting_**),但實際上只有一維陣列的資料存取
-對應到線性記憶體
-Successivesubscriptoperatorsdesignateanelementofamultidimensionalarrayobject.IfEisann-dimensionalarray(n≥2)withdimensionsi×j×...×k,thenE(usedasotherthananlvalue)isconvertedtoapointertoan(n−1)-dimensionalarraywithdimensionsj×...×k.Iftheunary*operatorisappliedtothispointerexplicitly,orimplicitlyasaresultofsubscripting,theresultisthepointed-to(n−1)-dimensionalarray,whichitselfisconvertedintoapointerifusedasotherthananlvalue.Itfollowsfromthisthatarraysarestoredinrow-majororder(lastsubscriptvariesfastest)
-Considerthearrayobjectdefinedbythedeclaration`intx[3][5];`Herexisa3×5arrayofints;moreprecisely,xisanarrayofthreeelementobjects,eachofwhichisanarrayoffiveints.Intheexpression`x[i]`,whichisequivalentto`(*((x)+(i)))`,xisfirstconvertedtoapointertotheinitialarrayoffiveints.Theniisadjustedaccordingtothetypeofx,whichconceptuallyentailsmultiplyingibythesizeoftheobjecttowhichthepointerpoints,namelyanarrayoffiveintobjects.Theresultsareaddedandindirectionisappliedtoyieldanarrayoffiveints.Whenusedintheexpression`x[i][j]`,thatarrayisinturnconvertedtoapointertothefirstoftheints,so`x[i][j]`yieldsanint.
-arraysubscripting在**編譯時期**只確認以下兩件事:
-得知size
-取得陣列第0個(即最初的一項)元素的指標
-前兩者以外的操作,都透過pointer
-arraysubscripting=>syntaxsugar
-Arraydeclaration:
```cpp
inta[3];
struct{doublev[3];doublelength;}b[17];
intcalendar[12][31];
```
那麼...
```cpp
sizeof(calendar)=?sizeof(b)=?
```
善用GDB,能省下沒必要的`printf()`,並可互動分析:(下方以GNU/Linuxx86_64作為示範平台)
-有時會遇到程式邏輯和結果正確,但因為`printf()`的輸出格式沒用對,而誤以為自己程式沒寫好的狀況
```shell
(gdb)psizeof(calendar)
$1=1488
(gdb)print1488/12/31
$2=4
(gdb)psizeof(b)
$3=544
```
還可分析型態:
```shell
(gdb)whatiscalendar
type=int[12][31]
(gdb)whatisb[0]
type=struct{...}
(gdb)whatis&b[0]
type=struct{...}*
```
更可直接觀察和修改記憶體內容:
```shell
(gdb)x/4b
0x601060:0x000000000x000000000x000000000x00000000
(gdb)pb
$4={{
v={0,0,0},
length=0
}
提昇輸出的可讀性
```shell
(gdb)setprintpretty
```
繼續觀察記憶體內容:
```cpp
(gdb)p&b[0]
$10=(struct{...}*)0x601060
(gdb)p(&b[0])->v
$11={0,0,0}
```
`p`命令不僅能print,還可變更記憶體內容:
```cpp
(gdb)p(&b[0])->v={1,2,3}
$12={1,2,3}
(gdb)pb[0]
$13={
v={1,2,3},
length=0
}
```
還記得前面提到`(float)7.0`和`(int)7`的不同嗎?我們來觀察執行時期的表現:
```cpp
(gdb)whatis(&b[0])->v[0]
type=double
(gdb)psizeof(&b[0])->v[0]
$14=8
```
Linuxx86_64採用[LP64datamodel](https://en.wikipedia.org/wiki/64-bit_computing#64-bit_data_models),`double`依據C語言規範,至少要有64-bit長度。
現在試著強制轉型:
```cpp
(gdb)p&(&b[0])->v[0]
$15=(double*)0x601060
(gdb)p(int*)&(&b[0])->v[0]
$16=(int*)0x601060
(gdb)p*(int*)&(&b[0])->v[0]
$17=0
```
竟然變成`0`,為什麼?!
因為:
```shell
(gdb)psizeof(int)
$18=4
```
我們只取出`v[0]`開頭的4bytes,轉型為`int`後,內容就是0。
印出記憶體來觀察:
```cpp
(gdb)x/4(int*)&(&b[0])->v[0]
0x601060:0x000000000x3ff000000x000000000x40000000
```
GDB強大之處不只如此,你甚至在動態時期可呼叫函式(改變執行順序),比方說`memcpy`:
```cpp
(gdb)pcalendar
$19={{0
前述輸出有個數字`6296224`,到底是什麼呢?試著觀察:
```cpp
(gdb)p(void*)6296224
$22=(void*)0x6012a0
從calendar把`{1,2,3}`內容取出該怎麼作呢?
```cpp
(gdb)p*(double*)&calendar[0][0]
$23=1
(gdb)p*(double*)&calendar[0][2]
$24=2
(gdb)p*(double*)&calendar[0][4]
$25=3
```
-Pointersmayhappentopointtoanelementofanarray
-Wecanusethatpointertoobtainapointertothenext/previouselementofthatarray
-Add/Subtract1forthatpointer
```cpp
inta[10];
...
int*p;
p=a;/*takethepointertoa[0]*/
p++;/*nextelement*/
p--;/*previouselement*/
```
-Addinganintegertoapointerisdifferentfromaddingthatintegertothebitrepresentationofthatpointer
```cpp
int*p;
p=p+1;/*thisadvancesp'svalue(pointed-address)bysizeof(int)whichisusuallynot1*/
```
-Thenameofthearrayisthesameasapointertothearrayineverycontextbutone
```cpp
inta[3];
int*p;
p=a;/*ppointstoa[0]*/
*a=5;/*setsa[0]to5*/
*(a+1)=5;/*setsa[1]to5*/
```
Theonlydifferenceisinsizeof:
```cpp
sizeof(a)/*returnsthesizeoftheentirearraynotjusta[0]*/
```
-Supposewewanttoconcatenatestringsandstringtintoasinglestring
```cpp
char*r;
strcpy(r,s);
strcat(r,t);
```
Doesn'tworkcauserdoesn'tpointanywhere.
Letsmakeranarray–nowitpointsto100chars
```cpp
charr[100];
strcpy(r,s);
strcat(r,t);
```
Thisworksaslongasthestringspointedtobysandtaren’ttoobig.
-Wewouldliketowritesomethinglike:
```cpp
charr[strlen(s)+strlen(t)];
strcpy(r,s);strcat(r,t);
```
HoweverCrequiresustostatethesizeofthearrayasconstant.
```cpp
char*r=malloc(strlen(s)+strlen(t));
strcpy(r,s);strcat(r,t);
```
Thisfailsforthreereasons:
1.`malloc()`mightfailtoallocatetherequiredmemory
2.Itisimportanttofreethememoryallocated
3.Wedidnotallocateenoughmemory
Thecorrectcodewillbe:
```cpp
char*r=malloc(strlen(s)+strlen(t)+1);//usesbrk;changeprogrambreak
if(!r)exit(1);/*printsomeerrorandexit*/
strcpy(r,s);strcat(r,t);
```
/*later*/
```cpp
free(r);
r=NULL;/*Trytoresetfree’dpointerstoNULL*/
```
`intmain(intargc,char*argv[],char*envp[])`的奧秘
```cpp
#include
根據你使用的地方不同,Array會有不同的意義:
*如果是用在expression,array永遠會被轉成一個pointer
*用在functionargument以外的declaration中它還是一個array,而且「不能」被改寫成pointer
*functionargument中的array會被轉成pointer
若現在這裡有一個全域變數(global)
```cpp
chara[10];
```
在另一個檔案中,我不能夠用`externchar*a`來操作原本的`a`,因為實際上會對應到不同的指令(instruction)。
但若你的declaration是在functionargument中,那麼:
*`voidfunction(chara[])`與`voidfunction(char*consta)`是等價的
*而且,真實的型態會是pointer
因此你不能用`sizeof`取得其佔用的空間!(array是unmodifiablel-value,所以除了被轉成pointer,它還會是一個不能再被設定數值的指標,因此需要加上`const`修飾。
)
最後,用在取值時,array的行為與pointer幾乎雷同,但array會是用兩步取值,而pointer是三步。
(array的位址本身加上offset,共兩步,而使用pointer時,cpu需先載入pointer位址,再用pointer的值當作位址並加上offset取值)
延伸閱讀
*[C語言雜談:指標和陣列(上)](https://www.cnblogs.com/baochuan/archive/2012/03/22/2410821.html)
*[C語言雜談:指標和陣列(下)](https://www.cnblogs.com/baochuan/archive/2012/03/26/2414062.html)
*[C語言的指標和記憶體洩漏](http://blog.jobbole.com/84548/)
-AcommonCpitfallistoconfuseapointerwiththedatatowhichitpoints
![](https://hackpad-attachments.s3.amazonaws.com/embedded2015.hackpad.com_s0rlzR8wVtm_p.299401_1446626702133_copy.png)
依據manpage[strdup(3)](http://linux.die.net/man/3/strdup):
>Thestrdup()functionreturnsapointertoanewstringwhichisaduplicateofthestrings.Memoryforthenewstringisobtainedwith[malloc(3)](http://linux.die.net/man/3/malloc),andcanbefreedwith[free(3)](http://linux.die.net/man/3/free).
`strdup()`實際上會呼叫`malloc()`,來配置足以放置給定字串的「新空間」。
##FunctionPointer
```cpp
intmain(){return(********puts)("Hello");}
```
為何可運作?
根據C99規格書
-C99[6.3.2.1]Afunctiondesignatorisanexpressionthathasfunctiontype
-**Except**whenitistheoperandofthe**sizeof**operatorortheunary**&**operator,a**functiondesignator**withtype‘‘functionreturningtype’’**isconvertedto**anexpressionthathastype‘‘**pointertofunctionreturningtype’**’.
-`*`isRightassociativeoperator
-C99[6.5.1]Itisanlvalue,afunctiondesignator,oravoidexpressioniftheunparenthesizedexpressionis,respectively,anlvalue,afunctiondesignator,oravoidexpression.
-C99[6.5.3.2-4]Theunary*operatordenotesindirection.**Iftheoperandpointstoafunction,theresultisafunctiondesignator**
(注意到designator的發音,KK音標為[͵dɛ**zɪg**ˋnetɚ])
[實驗程式碼](https://gist.github.com/JIElite/d429aedbc515973ad46cd353a43d2388)
程式碼`(********puts)`可看作`(*(*(*(*(*(*(*(*puts))))))))`,最裡面的括號`(*puts)`由[6.5.3.2-4]可知它是一個functiondesignator。
再根據[6.3.2.1],它最後會被轉為pointertofunctionreturningtype。
往外延伸一個括號`(*(*puts))`由於最裡面的括號是一個functiondesignator,再多一個*operator它還是一個functiondesignator,最後也會被轉為pointertofunctionreturningtype,依此類推最後`(********puts)`仍會是一個functiondesignator。
根據6.3.2.1
```cpp
void(*fptr)();
voidtest();
fptr=test;
```
test:void(),會被轉成functionpointer:void(*)()
fptrisapointertofunctionwithreturningtype,
根據6.5.3.2
`(*fptr)`isafunctiondesignator,afunctiondesignatorwillbeconvertedtopointer.
typeof(*fptr):void()
我們可利用gdb去查看這些數值,搭配print命令:
```shell
(gdb)printtest
$1={void()}0x400526
又,外面又有一個*operator,`(*fptr’`是個functiondesignator,最後還是會被轉化為pointertofunction
但`0x400526`這個數值是怎麼來的呢?
我們可用以下命令觀察:
```shell
$gcc-ofp-gfp.c
$objdump-dfp
```
參考輸出裡頭的一段:
```shell
0000000000400526
由於`puts`的functionprototype是`intputs(constchar*s)`,因此每次經過`*`operator運算後得到的結果是仍然是`int`。
所以`*`的數目不會影響結果。
最後return的值是根據s的長度加上`'\n'`。
而這個例子return給`main`的值是6
##Addressandindirectionoperators
對應到C99/C11規格書[6.5.3.2],`&`所能操作的operand只能是:
*functiondesignator-基本上就是functionname
*`[]`or`*`的操作結果:跟這兩個作用時,基本上就是相消
-`*`-operand本身
-`[]`-`&`會消失,而`[]`會被轉換成只剩`+`(註:原本`[]`會是`+`搭配`*`)
+例如:`&(a[5])==a+5`
*一個指向非bit-fieldorregisterstorage-classspecifier的object的lvalue
>[bit-field](https://hackmd.io/@sysprog/c-bitfield):一種在struct或union中使用用來節省記憶體空間的物件;
>特別的用途:沒有名稱的bit-field可以做為padding
除了遇到`[]`或`*`外,使用`&`的結果基本上都是得到pointertotheobject或是function的address
考慮以下程式碼:
```cpp
charstr[123];
```
為何`str==&str`呢?
*實際上左右兩邊的型態是不一樣的,只是值相同。
*左邊的是pointertochar:`char*`
-規格書中表示:除非遇到sizeof或是&之外,arrayoftype(在這就是指str)都會被直接解讀成pointertotype(在這就是pointertochar),而這個type是根據array的第一個元素來決定的
>Exceptwhenitistheoperandofthesizeofoperatorortheunary&operator,orisastringliteralusedtoinitializeanarray,anexpressionthathastype‘‘arrayoftype’’isconvertedtoanexpressionwithtype‘‘pointertotype’’thatpointstotheinitialelementofthearrayobjectandisnotanlvalue.(C996.3.2.1)
*右邊的則是pointertoanarray:`char(*)[123]`
-上面提到:遇到&時,str不會被解讀為pointertotype,而是做為原本的object,在這就是arrayobject,而addressofarrayobject也就是這個arrayobject的起始位址,當然也就會跟第一個元素的位址相同
-除了用值相同來解釋外,規格書在提到equalityoperators時,也有說到類似情境
>Twopointerscompareequalifandonlyifbotharenullpointers,botharepointerstothesameobject(includingapointertoanobjectandasubobjectatitsbeginning)orfunction(C996.5.9)
##針對指標的修飾(qualifier)
*指標本身不可變更(Constantpointertovariable):`const`在`*`之後
>char==*==constpContent;
*指標所指向的內容不可變更(Pointertoconstant):`const`在`*`之前
>constchar==*==pContent;
>charconst==*==pContent;
*兩者都不可變更
>constchar==*==constpContent;
##LearnCTheHardWay
-[書本資訊](https://learncodethehardway.org/c/)/[電子書](https://wizardforcel.gitbooks.io/lcthw/content/)
-[Exercise36:SaferStrings](https://wizardforcel.gitbooks.io/lcthw/content/ex36.html)
-"**WhyCStringsWereAHorribleIdea**"
-itisnotpossibletovalidateaCstringwithoutpossiblyloopinginfinitely.
-延伸閱讀:["NULL":電腦科學中最嚴重的錯誤,造成10億美元損失](https://linux.cn/article-6503-1.html)
-中華民國刑法第227條:「對於未滿十四歲之少男或少女為性交者,處三年以上十年以下有期徒刑...對於十四歲以上未滿十六歲之男女為性交者,處七年以下有期徒刑」
-刑法第227-1條(兩小無猜條款),規定未滿十八歲的人犯227條時,為告訴乃論,這是考量到雙方都是未成年,因為對性事好奇,或者是彼此相愛而進行性行為,故有特規定這個例外可通融,得免除或減輕其刑。
-[[出處](http://yblog.org/archive/index.php/11764)]規定要18歲以上才可看限制級內容的影片、圖畫和書刊,不過滿16歲就可合法性
-看限制級內容還不如實地操作才學得到東西(實作萬歲!)
-有沒有發現跟C-stylestring很像?都「經歷過」,才跟你說以前有「法律疑慮」
-延伸閱讀:[DidKen,Dennis,andBrianchoosewrongwithNUL-terminatedtextstrings?](http://queue.acm.org/detail.cfm?id=2010365)
-PerformanceCosts:什麼時候VisualBASIC會比C來得快?像是取得字串的長度,前者是常數時間,但後者就算做了[各式strlen()最佳化](https://coldnew.gitbooks.io/2015-embedded-summber/content/exame/detect_macro.html),終究耗時
-CompilerDevelopmentCost:對於`charmsg[256]`這樣的stringliteral,編譯器最佳化時,會嘗試用`memcpy()`取代`strcpy()`,前者通常有平台相依的最佳化,而後者因為不能事先知道長度,無法做更有效率的最佳化。
但這樣的最佳化技巧要讓編譯器自動施行,導致額外的維護成本
-SecurityCosts:一個`gets()`就有機會搞垮系統,對照看[InsecurecodinginC](http://www.slideshare.net/olvemaudal/insecure-coding-in-c-and-c)
-[Exercise15:PointersDreadedPointers](https://www.kancloud.cn/wizardforcel/lcthw/169213)
-[Exercise16:StructsAndPointersToThem](https://wizardforcel.gitbooks.io/lcthw/content/ex16.html)
-[Exercise18:PointersToFunctions](https://wizardforcel.gitbooks.io/lcthw/content/ex18.html)
##重新探討「字串」
-在電腦科學中,字串是由一連串的符號組成(注意:每單位可能不是1byte),每種程式語言對字串的實作方式或多或少會有不同,C語言沒有直接的語言層面支援,一般用"cstring"或稱為"c-stylestring"(C++觀點)稱呼,cstring又稱為是**ASCIIZ**或**null-terminatedstring**
-自C99以來,C有2種字串
1.bytestring:使用char作為最小操作單位,每個char至少要8bits
2.widestring:使用[wchar_t](http://pubs.opengroup.org/onlinepubs/007908775/xsh/wchar.h.html)作為最小操作單位,以便支援Unicode或多國語文(意味著變動程度編碼)
-大多數歐語的部分字母都有標示讀音的附加符號(俗稱accentmarks,學術上則稱diacritics),例如法語的**ç**、西班牙語的**ñ**、德語的**ü**、芬蘭語的**å**、波蘭語的**ł**、丹麥語的**ø**等等
-英語在歷史上曾經受過多國語言所沉浸和洗禮,本是歸屬於日耳曼語系,後又受過拉丁語、希臘語、法語、斯堪的納維亞語(即普遍的北歐語)等影響,以致今天英語的讀音變得非常不規則,相比其他歐語,英語的字母失去表音的功能,已不能從字母本身猜出正確讀音。
-延伸閱讀:[字母符號](http://languagemystery.blogspot.tw/2013/05/blog-post_30.html)
:::info
[UTF-8的發明人](https://www.cl.cam.ac.uk/~mgk25/ucs/utf-8-history.txt)也是主導UNIX發展的RobPike與KenThompson
[取自Wikipedia](https://en.wikipedia.org/wiki/C_string_handling):
>"SupportforUnicodeliteralssuchas`charfoo[512]="φωωβαρ";`(UTF-8)or`wchar_tfoo[512]=L"φωωβαρ";`(UTF-16orUTF-32)isimplementationdefined,andmayrequirethatthesourcecodebeinthesameencoding.Somecompilersoreditorswillrequireenteringallnon-ASCIIcharactersas`\xNN`sequencesforeachbyteofUTF-8,and/or`\uNNNN`foreachwordofUTF-16."
:::
*[[出處](http://luse.blogspot.tw/2009/06/blog-post.html)]為了使用上方便,許多程式語言提供**stringliterals**(在C就是C99[6.4.5]),讓開發者得以在程式碼內表示一些字串。
由於C語言是個系統程式語言,所以大家對這些東西會身在何處是有高度興趣的。
而stringliterals到底會身在何處,和編譯器的實作有關
*C語言規格中定義stringliterals會被分配於"staticstorage"當中(C99[6.4.5]),並說明如果程式嘗試修改stringliterals的內容,將會造成未定義行為
-以gcc的ELFtarget來說,將stringliterals分配在read-onlydatasection中(當然包含`\0`結尾)
-由於C語言提供了一些syntaxsugar來初始化陣列,這使得`char*p="helloworld"`和`charp[]="helloworld"`寫法相似,但底層的行為卻大相逕庭
-背景知識:[你所不知道的C語言:函式呼叫篇](https://hackmd.io/s/SJ6hRj-zg)關於stack的描述
-以指標的寫法`char*p`來說,意思是`p`將會是指向staticstorage的一個指標。
如此的寫法有潛在問題,因為當開發者嘗試修改stringliterals的內容,將會造成未定義行為,而編譯器並不會對存取p的元素提出警告
-值得注意的是,陣列的寫法依據C99規範,stringliterals是必須放在"staticstorage"中,而`charp[]`的語意則表示要把資料分配在stack內,所以這會造成編譯器(gcc)隱性地(implicitly)產生額外的程式碼,使得C程式在執行時期可把stringliterals從staticstorage複製到stack中。
雖然字串本身並非存放於stack內,但`charp[]`卻是分配在stack內,這也造成`returnp`是未定義行為
*光有字串表示法是遠遠不夠的,我們需要在字串上進行若干操作。
C語言的規格書中定義了標準函式庫,在[7.21]***Stringhandling\
接下來問題來了,這些字串處理函式的原型大部分是使用`char*`或`void*`的型別來當參數,那這些參數到底能不能接受nullpointer`(void*)0`呢?如果不能,那函式要負責檢查嗎?nullpointer算是一個字串嗎?對一個nullpointer使用這些函式(如`strlen`)會發生什麼事?規格書中有定義這些東西嗎?
-答案是,像`strlen`一類的"stringhandlingfunction"++**不可**++接受nullpointer作為參數,因為在絕大部分的狀況中,nullpointer並非合法字串(why?),所以將nullpointer代入`strlen()`也造成未定義行為
-多數函式庫實作(glibc)也不會在內做nullpointer檢查(有些函式庫的實作會使用編譯器的延伸語法來提出警告),這意味著,開發者要肩負檢查nullpointer的一切責任!
*回頭閱讀規格書:[7.21.1]提到以下:
>Variousmethodsareusedfordeterminingthelengthsofthearrays,butinallcasesa`char*`or`void*`argumentpointstotheinitial(lowestaddressed)characterofthearray.Ifanarrayisaccessedbeyondtheendofanobject,thebehaviorisundefined.
-nullpointer顯然在++**絕大部分**++的狀況都不符合這個規定,既然不符合規定,函式庫的實作顯然也不需要浪費心力去做檢查。
更不用說想要在一些物件導向的字串函式庫中使用string物件的nullpointer來做字串運算。
延伸閱讀
-[SDS:一個簡易動態字串處理機制](http://blog.jobbole.com/68119/)
##LinusTorvalds親自教你C語言
-[arrayargument的正確使用時機](https://hackmd.io/@sysprog/c-array-argument)
[source](https://lkml.org/lkml/2015/9/3/541)Arethereanycasesofmulti-dimensionalarrays?Becausethoseactuallyhavesemanticmeaningoutsideof`sizeof()`,justinthingslikeaddingoffsets.Egsomethinglike
```cpp
intfn(inta[][10])
```
-endsupbeingequivalenttosomethinglike
```cpp
intfn(int(*a)[10])
```
and`a+1`isactually40bytesaheadof`a`,soitdoes*not*actlike
an"int*".
(AndImighthavescrewedthatupmightily-Cmultidimensionalarrays
andtheconversionstopointersarereallyeasytogetconfusedabout.
WhichiswhyIhopewedon'thavethem)
-[藝術與核心](http://blog.linux.org.tw/~jserv/archives/001888.html)
```cpp
#defineARRAY_SIZE(x)(sizeof(x)/sizeof((x)[0]))
```
##Lvalue的作用
有兩個詞彙常令人誤解:`lvalue`(C和C++都有)和`rvalue`(C++特有)——兩者都是對運算式(expression)的分類方式,其存在可追溯到1960年代的[CPL程式語言](https://en.wikipedia.org/wiki/CPL_(programming_language))。
在[Valuecategories](https://en.cppreference.com/w/cpp/language/value_category)談及這部分的發展脈絡:CPL開始對運算式區分為左側模式(left-handmode)與右側模式(right-handmode),左、右是指運算式是在指定的左或右側,有些運算式只有在指定的左側才會有意義。
C語言有類似的分類方式,分為`lvalue`與其他運算式,似乎`l`暗示著left的首字母,不過C語言標準中明確聲明`l`是"locator"(定位器)的意思,lvalue定義為"locatorvalue",亦即lvalue是個物件的表示式(anexpressionreferringtoanobject),該物件的型態可以是一般的objecttype或incompletetype,但不可為`void`。
換句話說,運算式的結果會是個有名稱的物件。
到了C++98,非lvalue運算式被稱為`rvalue`,而部分在C語言隸屬「非lvalue的運算式」變成lvalue,後續在C++11,運算式又被重新分類為lvalue,prvalue,xvalue,lvalue與rvalue。
某些文件將lvalue和rvalue分別譯為左值、右值,這種翻譯方式顯然過時,且偏離C和C++語言規格的定義,我們要知道,lvalue和rvalue只是個分類名稱。
:::info
無論是C語言、B語言,還是Pascal語言,都可追溯到[ALGOL60](https://en.wikipedia.org/wiki/ALGOL_60),後者是AlgorithmicLanguage(演算法使用的語言)的縮寫,提出巢狀(nested)結構和一系列程式流程控制,今日我們熟知的if-else語法,就在ALGOL60出現。
ALGOL60和COBOL程式語言並列史上最早工業標準化的程式語言。
除了ALGOL60,直接影響C語言的程式語言是BCPL,後者是劍橋大學的MartinRichards於1967年訪問麻省理工學院時期所設計。
BCPL程式語言的提出,是回應同樣是劍橋大學發展的CPL(最初的意思是CambridgeProgrammingLanguage,後來由於和倫敦大學合作,更名為"CombinedProgrammingLanguage",也被戲稱為"CambridgePlusLondon")。
首篇提及CPL的論文發表於1963年,其設計接近硬體,程式設計的門檻高,而且不容易實作其編譯器,拖到1970年才有首個CPL的編譯器。
BCPL對CPL做了簡化,這個"B"字母就是"Basic"的意思,由於BCPL語言設計的精簡,所以其編譯器可在16KB的空間實作出來,也是最早[HelloWorld程式出現的程式語言](http://www.catb.org/jargon/html/B/BCPL.html)。
:::
在規格書的Rationale將lvalue定義為objectlocator(C99)
*C996.3.2.1footnote
>Thename"lvalue"comesoriginallyfromtheassignmentexpressionE1=E2,inwhichtheleftoperandE1isrequiredtobea(modifiable)lvalue.Itisperhapsbetterconsideredasrepresentinganobject"locatorvalue".Whatissometimescalled"rvalue"isinthisInternationalStandarddescribedasthe"valueofanexpression".Anobviousexampleofanlvalueisanidentifierofanobject.Asafurtherexample,ifEisaunaryexpressionthatisapointertoanobject,*EisanlvaluethatdesignatestheobjecttowhichEpoints.
舉例來說:
```cpp
inta=10;
int*E=&a;
++(*E);//a=a+1
++(a++);//error
```
E就是上面C99註腳提及的"apointertoanobject"(這裡的object指的就是a佔用的記憶體空間),下面列舉E這個identifier不同角度所代表的身份:
*`E`
+object:儲存addressofintobject的區域
+lvalue:Eobject的位置,也就是Eobject這塊區域的address
*`*E`
+lvalue:對E這個object做dereference也就是把Eobject所代表的內容(addressofintobject)做dereference,也就得到了intobject的位置,換個說法就是上面所提到的lvaluethatdesignatestheobjectwhichEpoints
以gcc編譯上方程式碼會產生
>error:**lvalue**requiredasincrementoperand
錯誤訊息,因為`a++`會回傳a的value,而這個value是暫存值也就是個non-lvalue,而`++()`這個operator的operand必須要是一個lvalue,因為要寫回data,需要有地方(location)可寫入
*延伸閱讀:[UnderstandinglvaluesandrvaluesinCandC++](http://eli.thegreenplace.net/2011/12/15/understanding-lvalues-and-rvalues-in-c-and-c)
透過Lvalue解釋compoundliteral效果的[案例](http://stackoverflow.com/questions/14105645/is-it-possible-to-create-an-anonymous-initializer-in-c99/14105698#14105698):
```cpp
#include
![](https://hackpad-attachments.s3.amazonaws.com/embedded2015.hackpad.com_s0rlzR8wVtm_p.299401_1474171871668_undefined)
>[source](https://www.facebook.com/photo.php?fbid=10206570192283054&set=p.10206570192283054)
考慮以下程式(null.c)
```cpp
intmain(){return(NULL==0);}
```
用gcc-7.2編譯:
```shell
null.c:1:22:'NULL'undeclared(firstuseinthisfunction)
intmain(){return(NULL==0);}
^~~~
null.c:1:22:note:eachundeclaredidentifierisreportedonlyonceforeachfunctionitappearsin
```
表示NULL需要定義,我們加上`#include
那`main`的回傳值是0還是1(或非零)呢?
-依據C99規格6.3.2.3
-nothingrequirestheNULL**pointer**tohavethenumericvalueofzero;
-這點導致實做上的歧異
-C99規格6.3.2.3-3
-A“null**pointer**constant”doesnothavetobea“null**pointer**”
*[C++11-NULLPTR!=NULL](https://cppturkiye.wordpress.com/2016/02/05/c11-nullptr-null/)
*[空指標常數(nullpointerconstant)](https://www.ptt.cc/bbs/C_and_CPP/M.1461840781.A.192.html)
*依據C99/C11規格**6.3.2.3**-3
-"Anintegerconstantexpressionwiththevalue0,orsuchanexpressioncasttotypevoid*,iscalledanullpointerconstant.66)Ifanullpointerconstantisconvertedtoapointertype,theresultingpointer,calledanullpointer,isguaranteedtocompareunequaltoapointertoanyobjectorfunction."
-在caller端轉換數值為pointertype時,就會變為nullpointer。
-延伸C99/C11規格**6.3.2.3**-4
-Conversionofanullpointertoanotherpointertypeyieldsanullpointerofthattype.Anytwonullpointersshallcompareequal.
-(void*)(uintptr_t)0本質上是個nullpointer,而NULL是nullptr,以C99的觀點。
-C99規格6.7.8Initialization
-當透過malloc()建立的物件經由free()釋放後,應該將指標設定為NULL
-避免doubly-free
-`free()`傳入NULL是安全的
傳統的實做方式如下:
```cpp
#defineoffsetof(st,m)((size_t)&(((st*)0)->m))
```
這對許多C編譯器(像是早期的gcc)可正確運作,但依據C99規格,這是undefinedbehavior。
後來的編譯器就不再透過巨集來實做,而改用builtinfunctions,像是gcc:
```cpp
#defineoffsetof(st,m)__builtin_offsetof(st,m)
```
這對於C++非常重要,否則原本的巨集連編譯都會失敗。
延伸閱讀:[C99的offsetofmacro](http://blog.linux.org.tw/~jserv/archives/001399.html)
Linux核心延伸offsetof,提供container_of巨集,作為反向的操作,也就是給予成員位址和型態,反過來找struct開頭位址:
```cpp
#definecontainer_of(ptr,type,member)({\
consttypeof(((type*)0)->member)*__mptr=(ptr);\
(type*)((char*)__mptr-offsetof(type,member));})
```
應用方式請見:[Linux核心原始程式碼巨集:`container_of`](https://hackmd.io/@sysprog/linux-macro-containerof)
##延伸閱讀
-[C語言常見誤解](https://docs.google.com/document/d/12cR0qb-kl7jB8RA_eHQu8OSmGKEOshwLeX4Mx5qBzUE/edit)byPTTlittleshan+Favonia
-注意:部份以32-bit架構為探討對象
-建議把前面幾節閱讀完,再來讀這篇FAQ
-漫談C語
-[豆瓣連結](http://www.douban.com/group/topic/55441892/)(需登入)
-[作者blog](https://sunxiunan.com/archives/1661)
-[CTrapsandPitfalls](http://www.literateprogramming.com/ctraps.pdf)
-[TheDescenttoC](http://www.chiark.greenend.org.uk/~sgtatham/cdescent/)
33
×
Signin
Email
Password
Forgotpassword
or
Byclickingbelow,youagreetoourtermsofservice.
SigninviaFacebook
SigninviaTwitter
SigninviaGitHub
SigninviaDropbox
SigninviaGoogle
NewtoHackMD?Signup
延伸文章資訊
- 1指標入門-淺談指標的藝術@ Bryan的C語言筆記 - 隨意窩
- 2你所不知道的C語言:指標篇 - HackMD
The construction of a pointer type from a referenced type is called ''pointer type derivation''. ...
- 3C語言: 超好懂的指標,初學者請進~ - 寫點科普Kopuchat
等等,那C 語言中的指標是長什麼樣子? 讓我們來看看這段程式碼: void main(){ int a = 15; int b = 2; int c = 39; ...
- 4C 速查手冊- 6.1 指標 - 程式語言教學誌
C 語言的指標(pointer) 是用來指向儲存指向某個記憶體位址的資料型態(data type) ,實際上我們須認識電腦管理記憶體好比一個長列,每一列都有以位元編碼的位址,每一位 ...
- 5[C 語言] 程式設計教學:指標(Pointer) 和記憶體管理(Memory ...
指標(pointer) 是C 語言的衍生型別之一。指標的值並非資料本身,而是另一塊記憶體的虛擬位址(address)。我們可利用指標間接存該指標所指向的記憶體的值。在C 語言中, ...