购物车0种商品
IC邮购网-IC电子元件采购商城
C语言指针相关问题讨论
(2011/10/8 9:25:00)
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)的操作是对一个地址的操作。

网友评论:编程语言可以看成是编译器的生产者与使用者之间的协议
既然是协议,当然要尽量公平呀,语言的制定者需要兼顾各方的利益
一些语法规则在使用者看来是“不合理”甚至“缺陷”,但编译器却喜欢
所以理解编译器的实现非常有助于理解语言的内涵

浏览:(990)| 评论( 0 )
博文评论

  • 昵 称:
  • 内 容:10~250个字符
  • 验证码: 验证码看不清楚?请点击刷新验证码
  •                      
  • IC电子元件查询
    IC邮购网电子元件品质保障