main()
{
inta[5]={1,2,3,4,5};
int*ptr=(int*)(&a+1);
printf("
%d,%d",*(a+1),*(ptr-1));
}
輸出結果是多少?
請解釋一下原因...
網友評論:char*a;和chara[];的區別。
網友評論:這個問題也沒有必要討論下去了
PS:怎么加簽名?
網友評論:只有一種規則!它實際上是以一種相當優雅的方式把我們這些完全不同的概念聯系在一起。
《C與指針》P141:
首先讓我們學習一個概念,它被許多人認為是C語言設計的一個缺陷。但是,這個概念實際上是以一種相當優雅的方式把一些完全不同的概念聯系在一起的。
考慮下面的聲明:
inta[5];
a[1]的類型是整型,那a的類型又是什么?它所表示的又是什么?一個合乎邏輯的答案是它表示整個數組,但事實并非如此。
在C中,在幾乎所有使用數組名的表達式中,數組名的值是一個指針常量,也就是數組第一個元素的地址。它的類型取決于數組元素的類型:如果它們是int類型,那么數組名就是“指向int的常量指針”;如果它們是其它類型,那么數組名的類型就是“指向‘其他類型’的常量指針”。
數組具有一些和指針完全不同的特征。例如,數組具有確定數量的元素,而指針只是一個標量值。編譯器用數組名來記住這些屬性。只有數組名在表達式中使用時編譯器才會為它產生一個指針常量。
注意這個值是指針常量,而不是指針變量,你不能修改常量的值。你只要稍微回顧一下,就會認為這個限制是合理的:指針常量所指向的是
內存中數組的起始位置。如果修改這個指針常量,唯一可行的操作就是把整個數組移動到內存的其他位置。但是,程序完成鏈接之后,內存中數組的位置是固定的。所以當程序運行時,再想移動數組就為時已晚了。因此,數組名的值是一個指針常量。
只有在兩種場合下,數組名并不用指針常量來表示:
1、當數組名作為sizeof操作符的操作數時:sizeof返回整個數組的長度,而不是指向數組的指針的長度。
2、當數組名作為單目操作符的操作數時:取一個數組名的地址所產生的是一個指向該數組的指針,而不是一個指向某個指針常量值的指針。
個人看法:既然說了“在C中......”那么現今所有的C都符合這個“相當優雅的”規則。如果是“有的C編譯器這樣,另一些編譯器那樣,......”那么這本世界流行的《C與指針》還不如我們這場討論有價值?
網友評論:俺喜歡C++和delphi,討厭C,所以干什么都先想到"數組"或結構(類)~~~
例如俺這幾天發了一帖水文:DSP281X變量地址數組定位方法
細心的人會看出來:
優秀的編譯器對指針/數組/結構成員變量/結構指針的成員變量
等訪問最終的優化代碼幾乎是相同的.
00說的:在keil51上a==&a
俺認為可能純屬巧合~~~
如果:
xdataunsignedi;
若此時i==&i俺真的再不玩51了~~~
網友評論:51C編譯器在處理一個數組名/函數名時,首先從符號表中查詢有沒有這個名字:
1、如果有,那么它肯定也相應有固定的地址。這時將被編譯為立即尋址或者直接尋址方式訪問。如果用指針間接方式訪問,那純粹是“知易行難”。
2、如果符號表中沒有這個名字(動態建立?)將采用指針間接尋址方式——指針用于訪問無名地址。而數組和函數都是有名字的。
3、再舉個例子:
intf(int);
int(*pf)(int)=f;//本應寫為...=&f;這就像pf=&f;/pf=f;兩者對編譯器來說完全一樣,“&”操作符只是顯式地說明了編譯器將隱式執行的任務。下面的3種函數調用方式,結果一樣效率大不同。
f(55);//立即尋址
(*pf)(55);//間接尋址
pf(55);//間接尋址
后兩個式子編譯代碼完全一樣。比第一個長N倍。
網友評論:正如wxj1952所述,在編譯器優化時,C默認了很多強制轉換,很多在C++上是不允許的,因為這樣很不安全,應該顯式的轉換.
拿數組來說,第1個數值的位置就是數組的物理位置即存儲地址.結構也類同.
所以,A,&A,&A[0]都表示數組的首地址.
例如:
&A[0]&A[1]&A[2]
A數組地址0x1000,0x1002,0x1004
A[0]A[2]A[2]
A數組數值0x00,0x01,0x02
而對于一般變量來說
&a
a變量的地址0x2000
a
a變量的數值0x00
用c語言來表述:
unsignedintA[3]at0x1000={0,1,2};
unsignedintaat0x2000=0;
若:
unsignedint*p;
p=A;
p=&A[0];
p=A[0];肯定錯因為左值是指針變量p,應該是指針即地址
而右值是數組第1個單元A[0]的數值而非地址本身!
但是我的A[0]內就是我需要的地址呢???當然是要做強制轉換了
即:
p=(unsignedint*)A[0];
但是一般變量很容易誤導.
p=&a;//正確
p=a;//我暈!!!a的地址&a是0x2000,而0x2000的內容a是0.
以下都能通過嗎???
p=(unsignedint*)0x2000;//知道a的地址
if(p==&a)
{
puts("這個俺明白");
}
if(p==a)
{
puts("這個是做夢吧,除非編譯器認為左值是指針右值肯定為指針即a的地址");
}
else
{
puts("俺堅持p!=a的說明");
}
故菜農認為:
即使編譯器認為左值是指針,右值肯定為指針即地址.
那么也應該養成區分a和&a的好習慣~~~
否則程序移植到C++上就要漫天找錯了~~~
網友評論:高級語言中定義的各種類型,在底層實現上都會失去其意義,所以底層代碼處理數組,結構抑或是指針的方式是一樣的
另外,C++和Delphi?應該是C++和ObjectPascal吧。
偶稀飯Pascal的原因也包含hotpower所說的2點,1是可以不使用指針,減少出錯的機會;2是強類型匹配。不過Delphi太貴,偶用Lazarus或者TurboDelphi
偶看的書少,我對C的理解只是簡單的按照規范文檔的說明。
我寫的代碼也從來沒有由于我理解不對而出問題,至于別人怎么理解就和我沒有關系了,不過我是建議不要使用默認的類型轉換
網友評論:圈圈說的a是指樓主inta[5]={1,2,3,4,5};中的數組名,
而hot好像理解為了一般標量xdataunsignedinta;
2個人說的兩回事。
數組名/函數名不是標量,編譯器將以一種優雅的方式特殊對待處理。
例如:
int*ptr=&a;或者int*ptr=a;都是一樣的。ptr都等于0x22(假定data地址。)
printf("
%#x,%#x",a,ptr);結果:0x22,0x22。
ptr=a;左值ptr是地址,右值a是常量指針a的值(0x22)。概念上沒問題。
網友評論:所以討論一個沒人用的寫法沒意義啊
映像中途只有看到過一次&a的用法
#definesizeof(a)((char*)(&(a)+1)-(char*)&(a))
順便提一下32樓的宏:
#defineFLASH(x)(*((volatileunsignedchar*)0x8000+x))
最好寫成
#defineFLASH(x)(*((volatileunsignedchar*)0x8000+(x)))
網友評論:非常完美~~~
希望Delphi迷喜歡~~~
相關鏈接:http://blog.ednchina.com/hotpower/11729/message.aspx
網友評論:哈哈~~~
當x為復雜的表達式時:
#defineFLASH(x)(*((volatileunsignedchar*)0x8000+x))
中x的有部分可能與0x8000先"亂搞"~~~~
#defineFLASH(x)(*((volatileunsignedchar*)0x8000+(x)))
俺以前就有過"慘痛"的教訓~~~
開始:
#defineLedChar0LedSegA+LedSegB+LedSegC+LedSegD+LedSegE+LedSegF
//...................................
實戰出錯找了一天才想到了是宏bug~~~
以后俺都是:
#defineLedChar0(LedSegA+LedSegB+LedSegC+LedSegD+LedSegE+LedSegF)
#defineLedChar1(LedSegB+LedSegC)
#defineLedChar2(LedSegA+LedSegB+LedSegD+LedSegE+LedSegG)
#defineLedChar3(LedSegA+LedSegB+LedSegC+LedSegD+LedSegG)
#defineLedChar4(LedSegB+LedSegC+LedSegF+LedSegG)
#defineLedChar5(LedSegA+LedSegC+LedSegD+LedSegF+LedSegG)
#defineLedChar6(LedSegA+LedSegC+LedSegD+LedSegE+LedSegF+LedSegG)
#defineLedChar7(LedSegA+LedSegB+LedSegC)
#defineLedChar8(LedSegA+LedSegB+LedSegC+LedSegD+LedSegE+LedSegF+LedSegG)
#defineLedChar9(LedSegA+LedSegB+LedSegC+LedSegD+LedSegF+LedSegG)
#defineLedCharA(LedSegA+LedSegB+LedSegC+LedSegE+LedSegF+LedSegG)
#defineLedCharB(LedSegC+LedSegD+LedSegE+LedSegF+LedSegG)
#defineLedCharC(LedSegA+LedSegD+LedSegE+LedSegF)
#defineLedCharD(LedSegB+LedSegC+LedSegD+LedSegE+LedSegG)
#defineLedCharE(LedSegA+LedSegD+LedSegE+LedSegF+LedSegG)
#defineLedCharF(LedSegA+LedSegE+LedSegF+LedSegG)
相關鏈接:/club/bbs/ShowAnnounce.asp?id=2928997
網友評論:39樓給出的宏定義:
映像中途只有看到過一次&a的用法
#definesizeof(a)((char*)(&(a)+1)-(char*)&(a))
雖然俺不知是干什么的,但卻聯想到:
&a+1==a+1嗎???
網友評論:根本來說,由于C的歷史所致,對C中數組名的“定義”是存在二義性的。好在存在一個自然的解決途徑,就是“域外定義”。
正如我前面所述,對于一個指針而言談論其尺寸大小是沒有意義的,因此作為sizeof的定義拓廣,令sizeof(cA)為cA數組的整體尺寸不會存在什么矛盾。另外由于cA是一個常量指針,取其地址也是沒有意義的,同樣作為定義拓廣將&cA理解成一個指向數組整體的指針也未成不可。
一般情況下,二義性沒啥不好,只要不出現歧義就行。在標準C++中,由于多重繼承(VC未采用)就導致了二義性的出現。因為沒法采取“域外定義”,為了避免歧義的出現,就加入了一系列的額外規定(如虛類等)。
但從另外的角度來看,作為一個好的習慣,盡量別去碰那些費腦漿的二義性。于人于己都沒啥好處。
網友評論:LZ的題是C程序,在所有最新C編譯器下均編譯通過。37樓用C++編譯器編譯通不過,于是,以此為論據,用C編譯器的人,都是概念不清。
這討論的是C數組與指針的概念,拿C++來做論據,以證明自己是對的,不太好吧。
“取一個數組名的地址所產生的是一個指向該數組的指針,而不是一個指向某個指針常量值的指針。”
Kenneth的這本經典著作是不是寫錯了?抑或C語言創造者比我們遜多了?
網友評論:GCCOK了吧?
編譯器:AVR-GCC4.3
inta[3]={1,2,3};
int*p=&a+1;//warning:initializationfromincompatiblepointertype
inttmp=*p;//warning:arraysubscriptisabovearraybounds
指針類型不兼容為什么只是一個警告?GCC為什么不把類型不兼容的詳細類型列出?
編譯器的差別而已,GCC會自動轉換類型,并發出一個警告,而操作上都是一樣的
第二個警告是不是有些驚悚?
其實是因為優化登記,使用-O0就沒有第二個警告,這里也可以看出GCC的優化方式(猜想):簡化為inttmp=*(int*)(&a+1);
“取一個數組名的地址所產生的是一個指向該數組的指針,而不是一個指向某個指針常量值的指針。”是你說的那本書里的?
網友評論:#defineFLASH((volatileunsignedchar*)0x8000)
/*----------------------------------------------------------
另類的"數組"訪問,外擴的SST39VF800A從0x8000開始
voidFlashObj::ChipErase(void)
{
FLASH[0x5555]=0xaa;
FLASH[0x2AAA]=0x55;
FLASH[0x5555]=0x80;
FLASH[0x5555]=0xaa;
FLASH[0x2AAA]=0x55;
FLASH[0x5555]=0x10;
Wait(0x5555);
}
voidFlashObj::Wait(unsignedintaddress)
{
unsignedinttemp,val;
do{
val=FLASH[address];
__nop();
temp=FLASH[address];
__nop();
}
while(((val^temp)&(1<<BIT6))!=0);
}
----------------------------------------------------------*/
思考一下,在此狀況下是否還存在sizeof(FLASH)和&FLASH。
網友評論:Constraints
1Oneoftheexpressionsshallhavetype‘‘pointertoobjecttype’’,theotherexpressionshall
haveintegertype,andtheresulthastype‘‘type’’.
Semantics
2Apostfixexpressionfollowedbyanexpressioninsquarebrackets[]isasubscripted
designationofanelementofanarrayobject.Thedefinitionofthesubscriptoperator[]
isthatE1[E2]isidenticalto(*((E1)+(E2))).Becauseoftheconversionrulesthat
applytothebinary+operator,ifE1isanarrayobject(equivalently,apointertothe
initialelementofanarrayobject)andE2isaninteger,E1[E2]designatestheE2-th
elementofE1(countingfromzero).
加上回答:
我想sizeof(FLASH)存在,&FLASH不存在(未驗證)
網友評論:“取一個數組名的地址所產生的是一個指向該數組的指針,而不是一個指向某個指針常量值的指針。”是你說的那本書里的?
*********************************************************************
LZ題目:
main()
{
inta[5]={1,2,3,4,5};
int*ptr=(int*)(&a+1);
printf("
%d,%d",*(a+1),*(ptr-1));
}
輸出結果是多少?
請解釋一下原因...
*********************************************************************
以下結果不是推論出來的,是keilC51編譯結果。并且符合教本上的概念。
*(a+1)即a[1],就是2。*(ptr-1)就是a[0],即1。
什么教本上的什么概念?
《C和指針》P141:
首先讓我們學習一個概念,它被許多人認為是C語言設計的一個缺陷。但是,這個概念實際上是以一種相當優雅的方式把一些完全不同的概念聯系在一起的。
考慮下面的聲明:
inta[5];
a[1]的類型是整型,那a的類型又是什么?它所表示的又是什么?一個合乎邏輯的答案是它表示整個數組,但事實并非如此。
........(詳見31樓貼)
只有在兩種場合下,數組名并不用指針常量來表示:
1、當數組名作為sizeof操作符的操作數時:sizeof返回整個數組的長度,而不是指向數組的指針的長度。
2、當數組名作為單目操作符的操作數時:取一個數組名的地址所產生的是一個指向該數組的指針,而不是一個指向某個指針常量值的指針。
————摘自《POINTERSONC》KennethA.Reek著
實驗做過了。與KennethA.Reek教本中講述的概念一致:
inta[5];
int*ptr=&a;或者int*ptr=a;都是一樣的。ptr都等于0x22(假定data地址。)
printf("
%#x,%#x",a,ptr);打印結果:0x22,0x22。
0x22是a[0]的地址&a[0],也是指向a數組的指針。
請用C51編譯器做一下,看看有沒有警告。是你的問題還是KennethA.Reek教本的問題?
網友評論:OK,你如果一定認為我錯了的話就隨便你,當然目前看來還沒有人同意我的觀點
另外,如果對我所說的無法理解的話,還是建議簡單的認為我說的是錯的
當然,我的理解不會受到任何影響
int*ptr=&a;或者int*ptr=a;都是一樣的。ptr都等于0x22
我很早就說過了,數值上是一樣的,類型上不一樣。前提:通過編譯,目前在我使用的編譯器中,還沒有能夠0e0w通過的
0e0w=0error0warning
真的到此位置吧,我認為已經闡述清楚了
toLX:
看來ISO(theInternationalOrganizationforStandardization)andIEC(the
InternationalElectrotechnicalCommission)干的還不錯
也就是說:如果int*p=&a;中&a要+1的話,就需要int*p=(int*)(&a+1);?
最后加上一段(不知道怎么放到簽名位置):
AllwhatIpublishisinthehopethatitwillbeuseful,butWITHOUTANYWARRANTY;withouteventheimpliedwarrantyofMERCHANTABILITYorFITNESSFORAPARTICULARPURPOSE.
網友評論:只是沒有強調E1是一個常量指針,——不可改變的值。不具有指針變量的性質。
故意寫成int*p=&a+1;不太好吧。看看LZ的寫法。
網友評論:連生成的匯編代碼都一模一樣,這個是在VC6下測試的代碼:
6:unsignedchari[5]={0,1,2,3,4};
00401038movbyteptr[ebp-8],0//由此可知i的地址為epb-8,我這里是一個臨時數組,被分配在棧中,通過ebp變址尋址來訪問
0040103Cmovbyteptr[ebp-7],1
00401040movbyteptr[ebp-6],2
00401044movbyteptr[ebp-5],3
00401048movbyteptr[ebp-4],4
7:unsignedchar*p;
8:p=(char*)(&i);
0040104Cleaeax,[ebp-8]//這是取地址的代碼,指針p的位置在ebp-0C處。
0040104Fmovdwordptr[ebp-0Ch],eax
9:p=i;
00401052leaecx,[ebp-8]//不取地址,生成的代碼完全一樣
00401055movdwordptr[ebp-0Ch],ecx
另外,我改成一個函數,test2,測試的結果也是一樣的,加不加&都一樣。
11:unsignedchar*p;
12:p=(char*)(&test2);
0040108Cmovdwordptr[ebp-0Ch],offset@ILT+0(_test2)(00401005)
13:p=(char*)test2;
00401093movdwordptr[ebp-0Ch],offset@ILT+0(_test2)(00401005)
另外,對于指針或者數組,里面的偏移量放前面也無所謂,
unsignedchari[5]={0,1,2,3,4};
unsignedchar*p;
p=i;
3[p]=2
;//非典用法,但工作正常
網友評論:
其中內容未列入隨筆,在此帖出以供參考
數組實際上是一個由分層次疊加構造而成的一種數據結構,其最底層的基礎就是下標操作符。作為一種操作符不僅可以和數組聯用,還可以通過重載改變其原始語義(具體可見新手版內C++隨筆-21)。
只要有了一個常量指針,配合下標操作符的使用,就可以形成一個數組的雛形(前提是具備一個以常量指針為起始點的存儲空間)。當然作為數組的雛形自然有其缺陷,那就是缺少數組的長度信息,以至于不可能得到其有效的整體特性。因此作為一個“完整”的數組定義還必須加入數組的長度信息。但作為一維數組,相關定義并未改變其“數組名”和下標操作符的基本屬性。
那么多維數組情形又將是如何的呢?顯然由于多維數組必須含有n-1維下標長度的信息(n>1),所以不可能存在上述數組的原始實現(僅用起始指針和下標)。那么多維數組具體又是如何定義的呢?先列出定義形式:
typeA[N1][N2]...[Nn];
其中n為數組的維數,N1...Nn為數組各維的長度。
類似的,若將A獨立拿出來看,它還是一個指向數組起始位置的常量指針。問題是在此情況下所指對象的“類型”是什么?是否還能象一維數組那樣所指類型為數組單元變量類型呢?顯然不行,因為這樣的話將失去多維數組中相當重要的長度信息。那么不指向數組單元變量又指向什么呢?答案是,A將被定義為指向“數組類型”type[N2]...[Nn]的指針。由此,實際上還可以得出這樣一個結論,數組A是被組織成N1個類型為type[N2]...[Nn]的單元的“一維數組”,其中*(A+i)為此“一維數組”的第i+1個單元(即i+1個數組的數組名)。以此類推,可以得到這樣一個表達式:
*(...*(*(A+i)+j)+...k)<=>A[j]...[k]
利用這種層次疊加構造法,順理成章地建立了從最原始的數組雛形,通過不斷地增加維長信息,最終到多維數組空間的形成機制。
下面列出一個具體的實例:
#include"stdafx.h"
intA[10][20][30];
intx,y;
intmain(intargc,char*argv[])
{
A[1][2][3]=1234;
*(*(*(A+3)+2)+1)=4321;
x=*(*(*(A+1)+2)+3);
y=A[3][2][1];
return0;
}
網友評論:正方可能是把一維數組和2維數組的概念搞混了。正方說“int*pt=a;取地址運算后,pt與a數值相同,類型不同。”再看看教本上怎么說的,《C與指針》P158:
inta[5],*pt=a;
是一個合法聲明。它為一個整形數組a分配內存,把pt聲明為一個指向整型的指針,并把它初始化為指向a數組的第1個元素。pt和a具有相同的類型:指向整型的指針。
pt和a均為指向int的指針,類型哪點不同?(不相同的類型可以作為左、右值?)a的類型怎么可能是int[5]?這是2維數組的概念吧。
正方認為,經過了int*pt=a;pt就“提升”成為了“指向整型數組的指針”,一個指向標量的指針pt就提升為了指向矢量的指針!?pt+1就指向了a[6]。
要證實這個猜想,很簡單,由下面程序,運行一下,看看結果就行了:
inta[5]={1,7,3,4,5};
int*pt=a;
intmatrix[3][5];
int(*ptx)[5]=matrix;//指向整型數組的指針說明。--2維數組。(怎樣聲
//明一個指向int數組的指針?)
intmain()
{
a[4]=*(pt+1);//按照正方的觀點,這是絕對通不過編譯的。(結果實際是合法的,它
//將a[1]賦給了a[4]。5被修改為7。)
pt=matrix;//行嗎?
pt=ptx;//行嗎?
}
HWM說得很清楚了,2維數組matrix中,matrix仍然是一個常量指針,而*(matrix+i)=matrix依然是常量指針,它指向int數組而不是指向int標量。對matrix取值操作*matrix將得到一個int標量matrix[0]而不是一個int數組向量的值。matrix和pt根本不是相同的類型。
*********************************************************************
正方:
“關鍵:&a+1是什么意思
&a是取a的地址,結果是指向a的類型的指針,對指針的+1是指針+指針指向的類型的長度
a的類型是int[5],那么&a的操作的結果是得到一個指向int[5]類型的指針,……”
反方:
這是很古老的C,還是2維數組概念用錯了地方?
網友評論:inta[N];
int*p=a;Noproblem
int*p=&a;
這種情況,編譯器應該明確的給出一個錯誤,如果不這樣做,應當被認為是編譯器的缺陷。一個嚴謹優秀的編譯器不應當出現不恰當的歧意,以免導致不必要的混亂。
網友評論:我的意思是:&a跟a的類型不一樣,但是值一樣的,它們都是數組a[]的首地址。
網友評論:inta[N];
int*p=a;
int*p=&a;
再看看教本是怎么說的:“(第3式中)初始化表達式中的&操作符是可選的,因為數組名被使用時總是由編譯器把它轉換為數組指針。&操作符只是顯式地說明了編譯器將隱式執行的任務。”
“初始化表達式中”,編譯器將&a和a一視同仁,正是它經過多重思慮而確定的“優雅的處理方法”。最新C編譯器這么做自有它的道理。以前古老的C編譯器正是按照56樓認為的“嚴謹優秀”而做的。實踐中發現了一些問題,經過多重改進,ANSIC才有了現在這種優雅的處理方式。
注意2個前提條件:
1、是對待數組名或函數名(向量標識符)的處理方式。而不是普通變量(標量)名。
2、是在初始化表達式中,編譯器的處理方式。
要是連這么點前提條件都注意不到就大喊“缺陷”,比ANSIC、Aenneth還高明?
網友評論:我也就不回復了,即使你認為我說的是錯誤的也沒有關系,但是如果我覺得可能會引起問題的話,還是講一下的好
我想知道
“(第3式中)初始化表達式中的&操作符是可選的,因為數組名被使用時總是由編譯器把它轉換為數組指針。&操作符只是顯式地說明了編譯器將隱式執行的任務。”
是哪本書中的?
按照C99的標準第46頁6.3.2.1的第三條,"arrayoftype"類型作為除sizeof或者&操作符外其他操作符的操作數時,其類型是"pointertotype"
根據第四條,函數明也類似
inta[3];
int*p=a;中,a是"pointertotype"
int*p=&a;中,a是"arrayoftype",而&a是"pointertoarrayoftype",當然,有些編譯器可以轉化為"pointertotype"使得編譯通過
int*p=&a+1;&a是"pointertoarrayoftype",也就是說指針&a指向的類型是"arrayoftype",以上例子中這個類型的長度為3*sizeof(int),而指針+n是“指針的值+n*sizeof(指針指向的類型)”,所以&a+1的值應該是&a[0]+1*(3*sizeof(int))
如果&a和a是一樣的話,那么:
int*p=&a+1;
int*p=a+1;
p在數值上都應該是一樣的(&a[1]),不過目前只聽00說Keil上是一樣的,不知道還有哪些編譯器上也是一樣的
網友評論:有時甚至是非法地址,因為訪問存在對齊的問題
網友評論:沒有任何人聲稱比ANSIC、Aenneth還高明,進一步說,ansic被公認有缺陷,但并無損ansic的地位,畢竟制定者是人,不是神。請就事論事!
"編譯器將&a和a一視同仁",這種聲稱是不恰當的,至少vc2005下,會給出一個錯誤。某一compiler沒有給出一個錯誤,應當是一個缺陷,道理已經說的很明白了,如果再扯到“高明“上去,純屬人品問題。
網友評論:在CodeVisionAVR下,p=(int*)(&a);提示說非法地址:illegaladdress。這個就無法驗證了。
在CARM編譯器下,&a+1跟a+1的值不一樣。
在RealView編譯器下,&a+1跟a+1的值也不一樣。
網友評論:
看一下這個式子是否能撥開迷霧:
*(...*(*(A))...)<=>A[0][0]...[0]
其中從A到*(...*(*(A))...)全都是常量指針(也可叫數組名),他們所指的“類型”分別是
A->type[N2]...[Nn]
...
*(...*(*(A))...)->type
而其所指地址就是A的“首地址”。
現在再來看&A是個什么玩意兒。它是由A為單元的一個更高層次的數組名(別名為常量指針),由上類推可以得出其所指類型為type[N1][N2]...[Nn]。但問題是這個所謂的數組根本就沒有定義,因此某種程度上可以認為&A是一個杜撰出來的玩意兒,沒有意義。所以才會出現不同的編譯器對&A會有不同的處理,這一點都不奇怪,雖然在C標準中對&A有相應的規定。也許C標準是認為存在一個未定義的數組type[1][N1][N2]...[Nn]作為其&A的拓廣。
網友評論:to60樓
只是想根據標準作個簡單的說明
所以都用int類型,一般int類型比較不容易出現你說的這些問題,可能LZ出題的時候也考慮到了
另外,和大小端有關就不是很理解了,是否小端中使用a[1]的話,大端中使用a[-1]?這個不是由編譯器搞定的么?
to63樓
謝謝LS講解,其實n數組可以簡單的當作類型為n-1維數組的1維數組來理解,所以沒有必要把問題復雜話
toall
inta[3];
認為a是int[3]類型,除&和sizeof之外,其他運算退化為int*
或者認為a是int*類型,在&和sizeof下,有特殊的運算方式
我都不認為錯,實際使用上,這2種理解得到同樣的結果
但如果說a和&a是等價的,那么我確實要說些什么了
網友評論:
附:數組的定義
數組是由一個指向首單元的常量指針(數組名)所引領的一組一定數量的連續單元所組成的數據結構。此常量指針的尺寸大小(尺寸的拓廣)定義為整個數組的大小。此常量指針的地址(地址的拓廣)定義為數組首單元的地址。多維數組typeA[N1][N2]...[Nn]定義為一個常量指針(數組名)為A,由N1個單元(類型為type[N2]...[Nn],數組名為*(A+i)的數組,i=0...N1-1)組成的一維數組。這里的單元要理解成相關數組的常量指針(數組名)。
由以上數組的定義可以對實例中的相關表達式作以下分析:
*(*(*(A+1)+2)+3)
先看A,它是一個指向數組首單元(類型為int[20][30])的常量指針(此單元為*A)。那么*(A+1)就是指向第二個單元(第二個類型為int[20][30]的數組的常量指針)。然后在此數組(常量指針或數組名為*(A+1))內再得到類型為int[30]的第三個數組的常量指針*(*(A+1)+2)。最后在這個數組(常量指針或數組名為*(*(A+1)+2))中指定類型為int的第四個單元*(*(*(A+1)+2)+3),即數組A的單元變量A[1][2][3]。
用地址算式可以表示如下
*((int*)A+(1*sizeof(*(A))+2*sizeof(*(*(A)))+3*sizeof(*(*(*(A)))))/sizeof(int))
其中
*(A+1)的地址為(int*)A+1*sizeof(*(A))/sizeof(int)
*(*(A+1)+2)的地址為(int*)A+(1*sizeof(*(A))+2*sizeof(*(*(A))))/sizeof(int)
*(*(*(A+1)+2)+3)的地址為(int*)A+(1*sizeof(*(A))+2*sizeof(*(*(A)))+3*sizeof(*(*(*(A)))))/sizeof(int)
網友評論:另一個例子是函數指針賦值,我推測compiler設計者對此類問題作過推敲:使用&的程序員絕對不會是想對數組首址或函數地址再取地址(因為那沒有意義,數組名是被compiler當作數組首址,一般來說是一個常數),而是想獲取數組名或函數名的地址,故而使用&.對用戶接口設計有經驗的人經常會遇到此類問題,你知道用戶意圖,但是否放寬容錯度則很難抉擇。
看看visualC,到了2005,語法嚴格很多,以前可以編譯通過的程序會給出很多錯誤,以至于我們這里的程序員不愿意升級。我個人認為ms做得對:被通過的缺陷比沒通過要可怕很多,這會直接或間接鼓勵犯錯。
網友評論:看看教材是怎樣說的:《C與指針》P258
——只有當確實需要時,你才應該使用多層間接訪問。不然的話,你的程序將會變得更龐大、更緩慢并且難于維護。
編譯器的差別:
...編譯器對表達式…,你是不是覺得它有點笨?…是的。這個編譯器確實有點舊,它的優化器也不是很聰明。現代的編譯器可能會表現的好一點,但也未必。和那些編寫差勁的源代碼,然后依賴編譯器去產生高效的目標代碼相比,直接編寫良好的源代碼顯然更好。……
網友評論:換了個編譯器測試下,更暈,結果發現&a是不允許的……
在CodeVisionAVR下,p=(int*)(&a);提示說非法地址:illegaladdress。這個就無法驗證了。
在CARM編譯器下,&a+1跟a+1的值不一樣。
在RealView編譯器下,&a+1跟a+1的值也不一樣。
網友評論:&a和a都表示a[]的首地址。
&a+1和a+1,目前只有在keil51下測試相等,其它測試不相等~~~~有的干脆編譯通不過~~~
所以還是別搞&a這樣的東西為妙~~~~~~
網友評論:這里有一個比喻:
我在六里橋,想到西客站。這是兩條背靠背的公交線,中間沒有公交路。但是可以穿胡同走過去,直線距離200米,步行不要5分鐘。如果乘坐公交車,就要繞一個大彎,大概2公里。10倍距離!
穿胡同我不認得路,于是決定打的。上車后跟司機說:“去西客站。”多說了一句廢話:“走公交線。”(&a)
這時的出租車司機有3種選擇:
1、載上我,穿胡同,不到半分鐘,10元錢到手。這就是C51編譯器的做法,立即尋址。3種選擇里以高效為原則。(a)
2、拒載。司機覺得走公交線繞遠劃不來,2公里也是10元。這就是有些編譯器拒絕p=&a;的做法:“你的要求我無法實現。”。“請下車,自己走過去,很近。”(拒絕編譯)
3、司機就按我的要求,繞一個大彎到西客站。低效就低效,不賺錢就不賺錢。一切服從客戶。這就是正方所希望的編譯器優秀而的功能。“我沒讓你立即尋址,我讓你間接尋址你就間接尋址,別太裝聰明了。”
*(*(&(&
看看教材是怎樣說的:《C與指針》P258
——只有當確實需要時,你才應該使用多層間接訪問。不然的話,你的程序將會變得更龐大、更緩慢并且難于維護。
間接尋址的最終目的是什么?在這里除了獲得數組a某個位置的值a外還能有什么?顯然,一個數組名a表示&a[0],如果為了訪問a的值,一定要通過&&a[0]去達到目的,它是“確實需要”的么?現代編譯器會識別出用戶的問題,要么拒載,要么自動選擇1、直接尋址方式a幫你取得那個位置的值。就是不可能選擇方式3,繞個大圈去取得a的值,結果導致你的程序比別人龐大10倍,甚至無法運行。它不至于那么笨,看不出有優化捷徑。
我想現代編譯器的工作方式是:即使按方式3操作,最后通過優化器,還是回到了方式1、立即尋址或直接尋址方式。
網友評論:在c/c++中,真正意義上的多維數組是不存在的,多數compiler內部還是一維,因為compiler知道各維大小,因而可以計算出各個元素的具體偏移。一旦沒有了上下文,比如多維數組作為指針變量被傳遞,compiler就無法使用多維數組,這也就是很多compiler對多維數組轉換為指針加以限制的原因。
其他一些語言,比如java,c#,多維數組有所不同,生成與堆上而不是棧上,可以認為真正意義上的多維數組,比如二維,是必須先生成一個直指針數組,用以存儲下一維各個數組的首址。
網友評論:在數組的定義中其實已經引入了指針概念,只是為了存儲和訪問的效率起見未將指針實體化(僅以概念化的常量指針的方式出現)。所以作為常量指針的“數組名”只能出現在表達式中,卻不可以作為實體承接數值。
個人認為,C/C++中關于數組的定義過于松散。這不僅表現在其類型定義上,還體現在其組成結構上。正因為數組名被定義為一個常量指針,卻又并非指向數組整體,所以使得數組看起來象是一串珍珠,一旦斷線就散落一地。
網友評論:匯編程序中經常用到查表方法。一組連續的相同類型數據,表首地址有一個標號TABLE:...數據聚合。基址MOVDPTR,#TABLE加偏移A間接尋址方式被用來獲得這個表中一個位置的數據——MOVCA,@A+DPTR。
映像到C是完全一樣的!例如:現有數組charTABLE[10];C程序中寫下DPTR=TABLE;編譯后的匯編語句為立即尋址方式:MOVDPTR,#TABLE。C語句ACC=TABLE[2];編譯為間接尋址MOVCA,@A+DPTR。
要是想知道匯編程序中TABLE標號的地址在哪里,是不是有點開玩笑?“它可能在哪里?”它除了標識這張表,標識它的內存固定位置之外,它有地址么?匯編語言中TABLE的值怎么可能表示這個表中分立的10個字節的數據?有意義?
網友評論:對于那些人為定義的東西只能死記硬背了!!
網友評論:不知道高手們為什么喜歡用有歧義的東西呢,想讓他們輸出什么多一條語句不就好了嗎?
網友評論:ptr的地址就是指向a[1],所以ptr-1就是a[0]=1
網友評論:
呵呵,大家都說完了嗎?本姑娘也來說幾句。
1、數組就是數組,函數就是函數,指針就是指針。
2、只有在需要對表達式求值時編譯器才會將數組名和函數名轉換為指針。
例如:通常情況下編譯器不需要對sizeof、& 的表達式進行求值即可確定操作結果,所以在這里數組名和函數名不會被轉換為指針。
3、標準C和傳統C對取地址運算符& 的解釋不同。
如果a是某種類型的數組,&a 的類型是該類型數組的指針,而傳統C則把&a當成a
4、大家不覺得 computer00 很可愛嗎?
computer00 是偶最喜歡的版主
上面第2點偶曾求證過GNU的Richard Stallman先生,Richard Stallman先生的回答很幽默,大意是:“當然。如果是你做編譯器,你也會這么干!”
網友評論:哇塞,LS mm來頭大。
網友評論:哇塞,LS mm來頭大。
mcuisp 發表于 2009-9-13 12:39
俺可不是大頭MM哦
大頭很難看滴
網友評論:知道了,LS是很漂亮的mm
名字就很美。
不知道是見面不如聞名呢?還是聞名不如見面
網友評論:書上的題目了1
網友評論:知道了,LS是很漂亮的mm
名字就很美。
不知道是見面不如聞名呢?還是聞名不如見面
mcuisp 發表于 2009-9-13 14:26
人如其名也,
聞名如見面。
網友評論:個人覺得, 正規的開發人員 ( 而不是玩技術的 ) 應該避開這些缺陷, 對于花哨玩意兒, 簡單弱智點好. 完全可以參考 C++ 甚至 Java 的某些風格去寫程序, 這些語言的一些規定其實也是在逐步修正 C 的問題.
網友評論:熱烈贊同樓上的說法!!!
對于這種繞腦筋的題,我現在懶得去看了。
實在有需要,用編譯器來實驗,呵呵。
網友評論:哈哈,這么熱鬧,原來是來了一位女俠.
網友評論:我覺得11樓的這個問題很明確,
網友評論:*(&cB) = *(&cA); // error, why?
cB和cA是一個常量,就是數據第一個元素的地址,所以你對一個常量取地址是錯誤的。例如你寫成這樣:&5一樣
網友評論:#include "stdafx.h"
char cA[10];
char cB[10];
int main(int argc, char* argv[])
{
*(&cB) = *(&cA); // error, why?
// 編譯器對 (&cA) 和 (&cB) 求值,得到指向“包含10個char類型元素的數組類型”的指針,即char (*)[10] ,隨后編譯器對 char (*)[10] 解引用,結果類型為“包含10個char類型元素的數組類型”。而數組是不能整體賦值的,所以產生語法錯誤。
*(cB) = *(cA); // ok
*(&cB[0]) = *(&cA[0]); // ok
// 上面兩種情況是一回事,編譯器將把數組名cA和cB轉換為指向數組首對象的指針,即 (char *) 。(注意:是“首對象”而不是“首地址” ,對C數組而言首對象就是數組的第一個元素,即cA[0] 和 cB[0] ,它們的類型是char)。隨后編譯器對 (&cA[0]) 和 (&cB[0]) 解引用,結果類型為“char類型”,并且是個可修改的左值。這是兩條合法的C語句。
return 0;
}
網友評論:這涉及C編譯器怎么處理左值與右值的規定;還有就是編譯器只不能訪問一個地址開始的整體,只能逐一訪問整體中的單個地址。對*(&cB)的操作代表的是一個整體的操作,對*(cB)的操作是對一個地址的操作。
網友評論:編程語言可以看成是編譯器的生產者與使用者之間的協議
既然是協議,當然要盡量公平呀,語言的制定者需要兼顧各方的利益
一些語法規則在使用者看來是“不合理”甚至“缺陷”,但編譯器卻喜歡
所以理解編譯器的實現非常有助于理解語言的內涵