打印

实际使用中没有问题的话,就**自己认为正确的就可以了

[复制链接]
楼主: wrainp
手机看帖
扫描二维码
随时随地手机跟帖
41
HWM| | 2008-5-18 08:00 | 只看该作者 回帖奖励 |倒序浏览

这个问题早就有人相当透彻地分析过了

根本来说,由于C的历史所致,对C中数组名的“定义”是存在二义性的。好在存在一个自然的解决途径,就是“域外定义”。

正如我前面所述,对于一个指针而言谈论其尺寸大小是没有意义的,因此作为sizeof的定义拓广,令sizeof(cA)为cA数组的整体尺寸不会存在什么矛盾。另外由于cA是一个常量指针,取其地址也是没有意义的,同样作为定义拓广将&cA理解成一个指向数组整体的指针也未成不可。

一般情况下,二义性没啥不好,只要不出现歧义就行。在标准C++中,由于多重继承(VC未采用)就导致了二义性的出现。因为没法采取“域外定义”,为了避免歧义的出现,就加入了一系列的额外规定(如虚类等)。

但从另外的角度来看,作为一个好的习惯,尽量别去碰那些费脑浆的二义性。于人于己都没啥好处。

使用特权

评论回复
42
wxj1952| | 2008-5-18 15:30 | 只看该作者

37楼请用C编译器。

LZ的题是C程序,在所有最新C编译器下均编译通过。 37楼用C++编译器编译通不过,于是,以此为论据,用C编译器的人,都是概念不清。
    这讨论的是C数组与指针的概念,拿C++来做论据,以证明自己是对的,不太好吧。
   
   “取一个数组名的地址所产生的是一个指向该数组的指针,而不是一个指向某个指针常量值的指针。”

     Kenneth的这本经典著作是不是写错了?抑或C语言创造者比我们逊多了?

使用特权

评论回复
43
simon21ic| | 2008-5-18 17:07 | 只看该作者

sorry,应该拿C编译器说明

GCC OK了吧?
编译器:AVR-GCC 4.3
int a[3] = {1,2,3};
int *p = &a + 1;        // warning: initialization from incompatible pointer type
int tmp = *p;        // warning: array subscript is above array bounds

指针类型不兼容为什么只是一个警告?GCC为什么不把类型不兼容的详细类型列出?
编译器的差别而已,GCC会自动转换类型,并发出一个警告,而操作上都是一样的
第二个警告是不是有些惊悚?
其实是因为优化登记,使用-O0就没有第二个警告,这里也可以看出GCC的优化方式(猜想):简化为int tmp = *(int*)(&a+1);

“取一个数组名的地址所产生的是一个指向该数组的指针,而不是一个指向某个指针常量值的指针。”是你说的那本书里的?

使用特权

评论回复
44
HWM| | 2008-5-18 18:24 | 只看该作者

借用hot的程序稍加回归,看看是否能拨开数组的外衣观其本


#define FLASH  ((volatile unsigned char *) 0x8000 )
/*----------------------------------------------------------
另类的"数组"访问,外扩的SST39VF800A从0x8000开始
void FlashObj::ChipErase(void)
{
  FLASH[0x5555] = 0xaa;  
  FLASH[0x2AAA] = 0x55;  
  FLASH[0x5555] = 0x80;
  FLASH[0x5555] = 0xaa;  
  FLASH[0x2AAA] = 0x55;  
  FLASH[0x5555] = 0x10;
  Wait(0x5555);
}
void FlashObj::Wait(unsigned int address)
{
unsigned int temp, val;
  do {
    val = FLASH[address];
    __nop();
    temp = FLASH[address];
    __nop();
  }
  while(((val ^ temp) & (1 << BIT6)) != 0);
}
----------------------------------------------------------*/

思考一下,在此状况下是否还存在sizeof(FLASH)和&FLASH。

使用特权

评论回复
45
wxj1952| | 2008-5-18 23:07 | 只看该作者

回46楼

“取一个数组名的地址所产生的是一个指向该数组的指针,而不是一个指向某个指针常量值的指针。”是你说的那本书里的?
*********************************************************************

LZ题目:
main()
{
  int a[5]={1,2,3,4,5};
   int *ptr=(int *)(&a+1);
   printf(" %d,%d",*(a+1),*(ptr-1));
}

输出结果是多少?
  请解释一下原因...
*********************************************************************

以下结果不是推论出来的,是keil C51编译结果。并且符合教本上的概念。
 *(a+1)即a[1],就是2。*(ptr-1)就是a[0],即1。

什么教本上的什么概念?

《C和指针》P141:
    首先让我们学习一个概念,它被许多人认为是C语言设计的一个缺陷。但是,这个概念实际上是以一种相当优雅的方式把一些完全不同的概念联系在一起的。

考虑下面的声明:
  int a[5]; 
   a[1]的类型是整型,那a的类型又是什么?它所表示的又是什么?一个合乎逻辑的答案是它表示整个数组,但事实并非如此。
    ........(详见 31 楼贴)

    只有在两种场合下,数组名并不用指针常量来表示:
1、当数组名作为sizeof操作符的操作数时:sizeof返回整个数组的长度,而不是指向数组的指针的长度。
2、当数组名作为单目操作符的操作数时:取一个数组名的地址所产生的是一个指向该数组的指针,而不是一个指向某个指针常量值的指针。

————摘自《POINTERS ON C 》    Kenneth A.Reek  著

实验做过了。与 Kenneth A.Reek 教本中讲述的概念一致:
 int a[5]; 
int *ptr=&a;  或者  int *ptr=a;  都是一样的。ptr都等于0x22(假定data地址。)
printf(" %#x,%#x",a,ptr);  打印结果:0x22,0x22。

0x22是a[0]的地址&a[0],也是指向a数组的指针。

请用C51编译器做一下,看看有没有警告。是你的问题还是Kenneth A.Reek 教本的问题?

使用特权

评论回复
46
simon21ic| | 2008-5-18 23:21 | 只看该作者

sdcc默认就是C51的编译器

OK,你如果一定认为我错了的话就随便你,当然目前看来还没有人同意我的观点
另外,如果对我所说的无法理解的话,还是建议简单的认为我说的是错的
当然,我的理解不会受到任何影响

int *ptr=&a;  或者  int *ptr=a;  都是一样的。ptr都等于0x22
我很早就说过了,数值上是一样的,类型上不一样。前提:通过编译,目前在我使用的编译器中,还没有能够0e0w通过的

0e0w = 0 error 0 warning

真的到此位置吧,我认为已经阐述清楚了

to LX:
看来ISO (the International Organization for Standardization) and IEC (the
International Electrotechnical Commission)干的还不错
也就是说:如果int *p = &a;中&a要+1的话,就需要int *p = (int*)(&a + 1);?

最后加上一段(不知道怎么放到签名位置):
All what I publish is in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

使用特权

评论回复
47
wxj1952| | 2008-5-18 23:28 | 只看该作者

49楼的数组描述没错。

只是没有强调E1是一个常量指针,——不可改变的值。不具有指针变量的性质。

故意写成 int *p = &a + 1;  不太好吧。看看LZ的写法。

使用特权

评论回复
48
computer00| | 2008-5-19 03:21 | 只看该作者

晕……我试过了,对数组名取地址的值跟不取地址的值是一

连生成的汇编代码都一模一样,这个是在VC6下测试的代码:
6:        unsigned char i[5]={0,1,2,3,4};
00401038   mov         byte ptr [ebp-8],0   //由此可知i的地址为epb-8,我这里是一个临时数组,被分配在栈中,通过ebp变址寻址来访问
0040103C   mov         byte ptr [ebp-7],1
00401040   mov         byte ptr [ebp-6],2
00401044   mov         byte ptr [ebp-5],3
00401048   mov         byte ptr [ebp-4],4
7:     unsigned char * p;
8:     p=(char *)(&i);
0040104C   lea         eax,[ebp-8] //这是取地址的代码,指针p的位置在ebp-0C处。
0040104F   mov         dword ptr [ebp-0Ch],eax
9:        p=i;
00401052   lea         ecx,[ebp-8] //不取地址,生成的代码完全一样
00401055   mov         dword ptr [ebp-0Ch],ecx


另外,我改成一个函数,test2,测试的结果也是一样的,加不加&都一样。

11:    unsigned char * p;
12:    p=(char *)(&test2);
0040108C   mov         dword ptr [ebp-0Ch],offset @ILT+0(_test2) (00401005)
13:       p=(char *)test2;
00401093   mov         dword ptr [ebp-0Ch],offset @ILT+0(_test2) (00401005)

另外,对于指针或者数组,里面的偏移量放前面也无所谓,
unsigned char i[5]={0,1,2,3,4};
unsigned char * p;
p=i;
3[p]=2; //非典用法,但工作正常

使用特权

评论回复
49
HWM| | 2008-5-19 12:29 | 只看该作者

最终结案,我在C++教案中关于数组的一段描述:


其中内容未列入随笔,在此帖出以供参考


数组实际上是一个由分层次叠加构造而成的一种数据结构,其最底层的基础就是下标操作符。作为一种操作符不仅可以和数组联用,还可以通过重载改变其原始语义(具体可见新手版内 C++ 随笔-21)。

只要有了一个常量指针,配合下标操作符的使用,就可以形成一个数组的雏形(前提是具备一个以常量指针为起始点的存储空间)。当然作为数组的雏形自然有其缺陷,那就是缺少数组的长度信息,以至于不可能得到其有效的整体特性。因此作为一个“完整”的数组定义还必须加入数组的长度信息。但作为一维数组,相关定义并未改变其“数组名”和下标操作符的基本属性。

那么多维数组情形又将是如何的呢?显然由于多维数组必须含有n-1维下标长度的信息(n>1),所以不可能存在上述数组的原始实现(仅用起始指针和下标)。那么多维数组具体又是如何定义的呢?先列出定义形式:

type A[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"

int A[10][20][30];
int x, y;

int main(int argc, char* argv[])
{
    A[1][2][3] = 1234;
    *(*(*(A+3)+2)+1) = 4321;
    x = *(*(*(A+1)+2)+3);
    y = A[3][2][1];

    return 0;
}

使用特权

评论回复
50
wxj1952| | 2008-5-20 14:20 | 只看该作者

LS概念清晰!

    正方可能是把一维数组和2维数组的概念搞混了。正方说“ int *pt=a;  取地址运算后, pt与a 数值相同,类型不同。” 再看看教本上怎么说的,《C与指针》P158:

    int  a[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]。
要证实这个猜想,很简单,由下面程序,运行一下,看看结果就行了:

int a[5]={1,7,3,4,5};
 int *pt=a;

int  matrix[3][5];
int (*ptx)[5]=matrix;  //指向整型数组的指针说明。--2维数组。(怎样声
                        //  明一个指向 int 数组的指针?)

int main( )
{
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维数组概念用错了地方?


使用特权

评论回复
51
IceAge| | 2008-5-20 20:36 | 只看该作者

同意 simon21ic 的观点

int  a[N];
int* p = a; No problem

int* p = &a;
这种情况,编译器应该明确的给出一个错误,如果不这样做,应当被认为是编译器的缺陷。一个严谨优秀的编译器不应当出现不恰当的歧意,以免导致不必要的混乱。

使用特权

评论回复
52
computer00| | 2008-5-20 21:25 | 只看该作者

类型当然不符了,所以要强制类型转换啊。

我的意思是:&a跟a的类型不一样,但是值一样的,它们都是数组a[]的首地址。

使用特权

评论回复
53
wxj1952| | 2008-5-20 22:33 | 只看该作者

回56楼

int  a[N];
int* p = a; 
int* p = &a;

再看看教本是怎么说的:“(第3式中)初始化表达式中的&操作符是可选的,因为数组名被使用时总是由编译器把它转换为数组指针。&操作符只是显式地说明了编译器将隐式执行的任务。”

“初始化表达式中”,编译器将&a和a 一视同仁,正是它经过多重思虑而确定的“优雅的处理方法”。最新C编译器这么做自有它的道理。以前古老的C编译器正是按照56楼认为的“严谨优秀”而做的。实践中发现了一些问题,经过多重改进,ANSI C 才有了现在这种优雅的处理方式。


注意2个前提条件:
1、是对待数组名或函数名(向量标识符)的处理方式。而不是普通变量(标量)名。
2、是在初始化表达式中,编译器的处理方式。

要是连这么点前提条件都注意不到就大喊“缺陷”,比ANSI C、Aenneth 还高明?


 

使用特权

评论回复
54
simon21ic| | 2008-5-21 06:15 | 只看该作者

如果你提出观点我认为没有问题的话

我也就不回复了,即使你认为我说的是错误的也没有关系,但是如果我觉得可能会引起问题的话,还是讲一下的好

我想知道
“(第3式中)初始化表达式中的&操作符是可选的,因为数组名被使用时总是由编译器把它转换为数组指针。&操作符只是显式地说明了编译器将隐式执行的任务。”
是哪本书中的?

按照C99的标准第46页6.3.2.1的第三条,"array of type"类型作为除sizeof或者&操作符外其他操作符的操作数时,其类型是"pointer to type"
根据第四条,函数明也类似

int a[3];
int* p = a;中,a是"pointer to type"
int* p = &a;中,a是"array of type",而&a是"pointer to array of type",当然,有些编译器可以转化为"pointer to type"使得编译通过
int* p = &a + 1;&a是"pointer to array of type",也就是说指针&a指向的类型是"array of type",以上例子中这个类型的长度为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上是一样的,不知道还有哪些编译器上也是一样的

使用特权

评论回复
55
ayb_ice| | 2008-5-21 08:28 | 只看该作者

输出结果和大小端有关,甚至和几位机有关

有时甚至是非法地址,因为访问存在对齐的问题

使用特权

评论回复
56
IceAge| | 2008-5-21 09:30 | 只看该作者

首先,鄙视wxj1952的为人

没有任何人声称比ANSI C、Aenneth 还高明,进一步说,ansi c 被公认有缺陷,但并无损 ansi c 的地位,毕竟制定者是人,不是神。请就事论事!

"编译器将&a和a 一视同仁", 这种声称是不恰当的,至少 vc 2005 下,会给出一个错误。 某一 compiler 没有给出一个错误,应当是一个缺陷,道理已经说的很明白了,如果再扯到 “高明“上去,纯属人品问题。

使用特权

评论回复
57
computer00| | 2008-5-21 10:11 | 只看该作者

换了个编译器测试下,更晕,结果发现&a是不允许的……

在CodeVisionAVR下,p= (int*)(&a); 提示说非法地址:illegal address。这个就无法验证了。

在CARM编译器下,&a+1跟a+1的值不一样。

在RealView编译器下,&a+1跟a+1的值也不一样。

使用特权

评论回复
58
HWM| | 2008-5-21 14:53 | 只看该作者

难怪有人说C中数组是最诡异东西,还没闹明白。


看一下这个式子是否能拨开迷雾:

    *(...*(*(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的拓广。

使用特权

评论回复
59
simon21ic| | 2008-5-21 15:18 | 只看该作者

有人同意我的看法了也

to 60楼
只是想根据标准作个简单的说明

所以都用int类型,一般int类型比较不容易出现你说的这些问题,可能LZ出题的时候也考虑到了
另外,和大小端有关就不是很理解了,是否小端中使用a[1]的话,大端中使用a[-1]?这个不是由编译器搞定的么?

to 63楼
谢谢LS讲解,其实n数组可以简单的当作类型为n-1维数组的1维数组来理解,所以没有必要把问题复杂话

to all
int a[3];
认为a是int[3]类型,除&和sizeof之外,其他运算退化为int*
或者认为a是int*类型,在&和sizeof下,有特殊的运算方式
我都不认为错,实际使用上,这2种理解得到同样的结果

但如果说a和&a是等价的,那么我确实要说些什么了

使用特权

评论回复
60
HWM| | 2008-5-21 18:28 | 只看该作者

既然各位对这个问题如此关注,就将压箱底的一点玩意儿帖

接54楼:

附:数组的定义

数组是由一个指向首单元的常量指针(数组名)所引领的一组一定数量的连续单元所组成的数据结构。此常量指针的尺寸大小(尺寸的拓广)定义为整个数组的大小。此常量指针的地址(地址的拓广)定义为数组首单元的地址。多维数组type A[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)

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则