首页 > 学技术 > 技术网文 > C/C++ > 正文

[精彩] 地址相减的结果?


来源 chinaunix.net kuqin整理

int *p1;

int *p2;
p1 = (int *)0x500;
p2 = (int *) 0x509;
value = p2-p1;
printf("value=%d \n",value);


这个value 应该等于多少,为什么?



 linuxiang 回复于:2006-08-17 15:47:13

0x9地址指向的四字节数据,也有可能出现segmentfault的情况,不知道楼主想做什么


 蚊见蚊爱 回复于:2006-08-17 15:58:41

引用:原帖由 linuxiang 于 2006-8-17 15:47 发表
0x9地址指向的四字节数据,也有可能出现segmentfault的情况,不知道楼主想做什么 


您就别误导别人了,

结果是2
这个看类型的
((void*)pAddr1 - (void*)pAddr2)/sizeof(type)

明白否?


 kuaizaifeng 回复于:2006-08-17 16:20:06

两个地址能相加吗?


 蚊见蚊爱 回复于:2006-08-17 16:36:22

不能相加


 蚊见蚊爱 回复于:2006-08-17 16:38:27

不能相加


 kuaizaifeng 回复于:2006-08-17 17:21:38

引用:原帖由 蚊见蚊爱 于 2006-8-17 16:38 发表
不能相加 



为啥不让相加??


 connet 回复于:2006-08-17 18:22:16

类型转换可以相加, 不过99%肯定溢出了.


 whyglinux 回复于:2006-08-17 19:34:13

引用:原帖由 zdmilan 于 2006-8-17 15:13 发表
int *p1;

int *p2;
p1 = (int *)0x500;
p2 = (int *) 0x509;
value = p2-p1;
printf("value=%d \n",value);


这个value 应该等于多少,为什么? 



其结果是无定义的。其原因:
[list=1][*]0x509如果被转换为int指针值的话其地址显然是不对齐的,因此这种转换行为是无定义的。
[*]两个指针的相减运算只有在指针都指向同一个数组对象(至多超过结尾的一个元素)的时候才是有定义的。除此之外,任意的两个指针的相减运算都是无定义的。
[/list]


 whyglinux 回复于:2006-08-17 19:37:53

引用:原帖由 kuaizaifeng 于 2006-8-17 16:20 发表
两个地址能相加吗? 



两个地址相加没有明显的物理意义,所以不允许。


 zdmilan 回复于:2006-08-17 20:11:51

引用:原帖由 whyglinux 于 2006-8-17 19:34 发表


其结果是无定义的。其原因:
[list=1][*]0x509如果被转换为int指针值的话其地址显然是不对齐的,因此这种转换行为是无定义的。
[*]两个指针的相减运算只有在指针都指向同一个数组对象(至多超过结尾的一个元 ... 



结果是确定的,value=2;
我觉得蚊见蚊爱的是正解!


 蚊见蚊爱 回复于:2006-08-18 09:39:55

引用:原帖由 zdmilan 于 2006-8-17 20:11 发表


结果是确定的,value=2;
我觉得蚊见蚊爱的是正解! 


下次就用这个面别人,哈哈


 tena 回复于:2006-08-18 10:18:32

引用:原帖由 zdmilan 于 2006-8-17 15:13 发表
int *p1;

int *p2;
p1 = (int *)0x500;
p2 = (int *) 0x509;
value = p2-p1;
printf("value=%d \n",value);


这个value 应该等于多少,为什么? 



回复:
value=2;
地址当然是可以相减的,不管什么类型,地址说白了不就是一个整数吗,两个整数当然可以相减(用这个办法还可以直接求字符串长度)。
等于2,是因为sizeof(int)=4, 9/4=2


 kuaizaifeng 回复于:2006-08-18 11:12:33

引用:
两个地址相加没有明显的物理意义,所以不允许。



哦,是这样的,谢谢了
想想看,好像两个地址相加确实没有什么物理意义


 kuaizaifeng 回复于:2006-08-18 11:13:50

引用:
地址当然是可以相减的,不管什么类型,地址说白了不就是一个整数吗,两个整数当然可以相减


那你把它当成整数加加看?
地址和整数还是不同的


 tena 回复于:2006-08-18 11:23:14

老兄,我这里只是针对相减而言。
其实具体相减的结果还和定义的数据类型有关,因为不同数据类型长度不同,
所以很多时候还可以用此办法来取字符串长度。
所以不同类型变量地址最好就不要用来相减了


 星尘细雨 回复于:2006-08-18 15:19:06

蚊兄,又见了。。


 蚊见蚊爱 回复于:2006-08-18 15:20:03

哥哥我最近郁闷啊


 zhangyxl 回复于:2007-05-12 12:10:24

明白了```~


 langue 回复于:2007-05-12 12:55:18

引用:原帖由 蚊见蚊爱 于 2006-8-17 15:58 发表

您就别误导别人了,

结果是2
这个看类型的
((void*)pAddr1 - (void*)pAddr2)/sizeof(type)

明白否? 



不同机器上的字长不同;再引用 whyglinux 的话说,那叫:行为无定义,所以原题没有确定解;你说的转换成 void * 的方法在实际使用中通常可行,但两者是不同的。

.


 MMMIX 回复于:2007-05-12 14:08:33

引用:原帖由 whyglinux 于 2006-8-17 19:34 发表
其结果是无定义的。其原因:
[list=1][*]0x509如果被转换为int指针值的话其地址显然是不对齐的,因此这种转换行为是无定义的。
[*]两个指针的相减运算只有在指针都指向同一个数组对象(至多超过结尾的一个元 ... 


正解。

再引用下 comp.lang.c 的 FAQ 中一些类似问题吧:
[u][url=http://www.c-faq.com/ptrs/int2ptr.html]Question 4.14[/u]
引用:
Q: How are integers converted to and from pointers? Can I temporarily stuff an integer into a pointer, or vice versa?

A: Once upon a time, it was guaranteed that a pointer could be converted to an integer (though one never knew whether an int or a long might be required), and that an integer could be converted to a pointer, and that a pointer remained unchanged when converted to a (large enough) integer and back again, and that the conversions (and any mapping) were intended to be ``unsurprising to those who know the addressing structure of the machine.'' In other words, there is some precedent and support for integer/pointer conversions, but they have always been machine dependent, and hence nonportable. Explicit casts have always been required (though early compilers rarely complained if you left them out).

The ANSI/ISO C Standard, in order to ensure that C is widely implementable, has weakened those earlier guarantees. Pointer-to-integer and integer-to-pointer conversions are implementation-defined (see question 11.33), and there is no longer any guarantee that pointers can be converted to integers and back, without change.

Forcing pointers into integers, or integers into pointers, has never been good practice. When you need a generic slot that can hold either kind of data, a union is a much better idea.

See also questions 4.15, 5.18, and 19.25.

References: K&R1 Sec. A14.4 p. 210
K&R2 Sec. A6.6 p. 199
ISO Sec. 6.3.4
Rationale Sec. 3.3.4
H&S Sec. 6.2.3 p. 170, Sec. 6.2.7 pp. 171-2 


[u][url=http://www.c-faq.com/osdep/rawmemadr.html]Question 19.25[/u]
引用:
Q: How can I access memory (a memory-mapped device, or graphics memory) located at a certain address?
How can I do PEEK and POKE in C?

A: Set a pointer, of the appropriate type, to the right number (using an explicit cast to assure the compiler that you really do intend this nonportable conversion):

unsigned int *magicloc = (unsigned int *)0x12345678;

Then, *magicloc refers to the location you want. [footnote] If the location is a memory-mapped I/O register, you will probably also want to use the volatile qualifier: ``volatile unsigned int *magicloc''. (If you want to refer to a byte at a certain address rather than a word, use unsigned char *.)

Under MS-DOS, you may find a macro like MK_FP() handy for working with segments and offsets. As suggested by Gary Blaine, you can also declare tricky array pointers which allow you to access screen memory using array notation. For example, on an MS-DOS machine in an 80x25 text mode, given the declaration

unsigned short (far * videomem)[80] =
(unsigned short (far *)[80])0xb8000000;

you can access the character and attribute byte at row i, column j with videomem[ i][j].

Many operating systems execute user-mode programs in a protected mode where direct access to I/O devices (or to any address outside the running process) is simply not possible. In such cases you will have to ask the operating system to carry out I/O operations for you.

See also questions 4.14 and 5.19.

References: K&R1 Sec. A14.4 p. 210
K&R2 Sec. A6.6 p. 199
ISO Sec. 6.3.4
Rationale Sec. 3.3.4
H&S Sec. 6.2.7 pp. 171-2 



总之一句话,这种做法问题多多,除非你很清楚自己在做什么,否则不要这么做 :em02:


 win_hate 回复于:2007-05-12 18:27:28

引用:原帖由 蚊见蚊爱 于 2006-8-17 15:58 发表

您就别误导别人了,

结果是2
这个看类型的
((void*)pAddr1 - (void*)pAddr2)/sizeof(type)

明白否? 



按标准,void 指针是不能与 void 指针相减的,此外 void 指针加减一个常数也是没有定义的。


 flw2 回复于:2007-05-12 18:40:57

movl    $1024, -4(%ebp)
        movl    $1033, -8(%ebp)
        movl    -4(%ebp), %edx
        movl    -8(%ebp), %eax
        subl    %edx, %eax
        sarl    $2, %eax

这就是
int *p = (int *)0x400;
        int *q = (int *)0x409;
        int a = q-p;
的代码

whyglinux 说的
0x509如果被转换为int指针值的话其地址显然是不对齐的,因此这种转换行为是无定义的。

应该是运行时的无定义,如果解引用有些体系结构上是错误的。但是这段代码本身一点问题都没有,

9/4 == 2,永远成立。


 cjaizss 回复于:2007-05-12 18:45:09

LZ想象力比较好:)
赞叹一把!
但是奇数边界指向int,这在很多机器上是不允许的,也就是说这个指针不能够拿来访问。


 MMMIX 回复于:2007-05-12 18:45:54

引用:原帖由 flw2 于 2007-5-12 18:40 发表
movl    $1024, -4(%ebp)
        movl    $1033, -8(%ebp)
        movl    -4(%ebp), %edx
        movl    -8(%ebp), %eax
        subl    %edx, %eax
        sarl    $2, %eax

这就是
int *p = (i ... 


把整数强制转换成指针存在移植性问题。


 flw2 回复于:2007-05-12 18:49:47

引用:原帖由 tena 于 2006-8-18 10:18 发表


回复:
value=2;
地址当然是可以相减的,不管什么类型,地址说白了不就是一个整数吗,两个整数当然可以相减(用这个办法还可以直接求字符串长度)。
等于2,是因为sizeof(int)=4, 9/4=2 



我上面的问题还是有问题,用9/4只是实现的问题,估计大多数实现都是这么做的,但是并不是一定的。
怎么求值是未定义的,但是如果它们的值相差是整数倍,而且必须是两个对象地址的差才肯定能保证正确,因为这样甚至都不对:

p = 0x401;
q= 0x409;
加入机器根本不能解引用pq,那么一味着编译器不会生成这种代码。也就是编译器计算p-q的真正方法在这种机器上还依赖于 sizeof int | p.


 langue 回复于:2007-05-12 18:52:38

如 win_hate 版主所言,本帖更为精彩。


 flw2 回复于:2007-05-12 18:52:58

引用:原帖由 MMMIX 于 2007-5-12 18:45 发表

把整数强制转换成指针存在移植性问题。 



对,所以这个还只是讨论而已,凡是无定义的都很可能能找到一个实现或系统不同我们通常所想象的那样


 flw2 回复于:2007-05-12 18:53:50

不过挺佩服楼主的想象力的,呵呵


 MMMIX 回复于:2007-05-12 19:23:16

引用:原帖由 flw2 于 2007-5-12 18:52 发表
对,所以这个还只是讨论而已,凡是无定义的都很可能能找到一个实现或系统不同我们通常所想象的那样 


C 语言的实现实在是太多太广了,各种各样、林林总总、不一而足。C 中的许多问题,即使是一些看似比较简单的问题,要想说的准确而详尽,都不是一件简单的事情,更非三言两语所能做到。


 choc 回复于:2007-05-13 14:34:41

其实地址相减还是经常遇到的,比如说做快速排序的时候!
我 这里整吵,对门一对正在嘿咻!!!
真是磨练人的意志啊!


 converse 回复于:2007-05-13 15:03:38

引用:原帖由 MMMIX 于 2007-5-12 19:23 发表

C 语言的实现实在是太多太广了,各种各样、林林总总、不一而足。C 中的许多问题,即使是一些看似比较简单的问题,要想说的准确而详尽,都不是一件简单的事情,更非三言两语所能做到。 




其实C本来很简单很美好的,只是很多人喜欢把简单的问题复杂化,于是C就不美好起来了。

其实我现在已经很少去看这些语言方面细枝末节的地方了,自己写代码的时候按照规范走,就不会出什么大的问题,可是我发现很多人喜欢钻这种问题的牛角尖,问一些“行为无定义”的问题。


 MMMIX 回复于:2007-05-13 15:09:29

引用:原帖由 converse 于 2007-5-13 15:03 发表
其实C本来很简单很美好的,只是很多人喜欢把简单的问题复杂化,于是C就不美好起来了。


最根本的原因还是 C 的实现太广了。
引用:
其实我现在已经很少去看这些语言方面细枝末节的地方了,自己写代码的时候按照规范走,就不会出什么大的问题,可是我发现很多人喜欢钻这种问题的牛角尖,问一些“行为无定义”的问题。


主要还是经验不足,另外就是受一些不好的书籍误导、又没有看那些经典优秀的书籍。

再有就是相同的语言在不同的人那里作用/意义是不同的,也许你只是用该语言写程序,因此一些隐秘的角落你不需要关心,但是对于需要实现该语言编译器/解释器的人来说,清楚的了解这些就是必要的了。

[ 本帖最后由 MMMIX 于 2007-5-13 15:13 编辑 ]


 fzy8888cn 回复于:2007-05-14 22:26:01

引用:原帖由 flw2 于 2007-5-12 18:40 发表
movl    $1024, -4(%ebp)
        movl    $1033, -8(%ebp)
        movl    -4(%ebp), %edx
        movl    -8(%ebp), %eax
        subl    %edx, %eax
        sarl    $2, %eax

这就是
int *p = (i ... 




昏死个人呀,最近正在学习汇编,看了半天没看明白.后来上网查才知道windows上的汇编和linux上的汇编不一样的.gcc采用的是AT&T的汇编格式,MS采用Intel的格式.谁能提供一本linux上汇编书.


 flw2 回复于:2007-05-14 22:41:35

汇编语言程序设计,Richard Blum


 HawaiiLeo 回复于:2007-05-15 10:59:42

感觉C语言比较灵活,简洁,正在学习中。


 yf_lw 回复于:2007-05-15 12:46:34

whyglinux 说得很对,标准规定,只有数组中的地址才可以减


 cobras 回复于:2007-05-15 13:18:31

通过指针相减来求元素个数具有不可移值性。
因为其结果有可能是错误的。LZ的例子是一个,另一个是内存模型(如:DOS编程)


 beepbug 回复于:2007-05-20 09:40:38

1)指针相减不等于地址相减。
2)如果指针指向一个数组,则指针可以加减一个整数,这表示指针在数组里的移动。两个都指向同一数组的指针,可以相减。
3)指针相减,具有可移植性。但是,有一个前提,你得把指针看作是指针。如果你把指针看作是地址,那它就没有可移植性。


 accelerator 回复于:2007-05-20 20:15:35

有点不明白:
如果因为边界对齐问题会导致崩溃, 那下边的结构体定义肯定是非法
#pragma pack(1)
struct s {
         char c;
         int i;
};
i完全可能不在一个对齐的地址上.

个人觉得, 指针减法并没有什么副作用, 真正需要小心的还是解引用这一步, 保证解引用的地址有效即可. 每看过标准, 感觉是这样.

[ 本帖最后由 accelerator 于 2007-5-20 20:17 编辑 ]


 fisdailar 回复于:2007-05-23 15:20:13

可以阿!不过value没有定义,要是定义为int,值为2!刚编译通过了!


 whyglinux 回复于:2007-05-23 20:44:59

引用:原帖由 cobras 于 2007-5-15 13:18 发表
通过指针相减来求元素个数具有不可移值性。
因为其结果有可能是错误的。LZ的例子是一个,另一个是内存模型(如:DOS编程) 



如果指针都指向同一个数组对象中的元素,这样的指针相减运算是有定义的,当然用来求两个指针之间的元素个数也是可移植的。


 whyglinux 回复于:2007-05-23 20:53:17

引用:原帖由 beepbug 于 2007-5-20 09:40 发表
1)指针相减不等于地址相减。
2)如果指针指向一个数组,则指针可以加减一个整数,这表示指针在数组里的移动。两个都指向同一数组的指针,可以相减。
3)指针相减,具有可移植性。但是,有一个前提,你得把指针看作是指针。如果你把指针看作是地址,那它就没有可移植性。



根据标准的规定,&a 表达式的含义是计算 a 的地址(address),它的结果也是一个指针(pointer)。在语言层面上说某一个对象的地址的时候,它不单具有一个具体的地址值,还总是伴随着这个对象的类型,所以地址其实等同于指针。

第 3 条有些唯心。


 accelerator 回复于:2007-05-23 23:05:21

引用:原帖由 accelerator 于 2007-5-20 20:15 发表
有点不明白:
如果因为边界对齐问题会导致崩溃, 那下边的结构体定义肯定是非法
#pragma pack(1)
struct s {
         char c;
         int i;
};
i完全可能不在一个对齐的地址上.

个人觉得, 指针减法并 ... 



有谁来回答我的疑问, THX.


 windy2335 回复于:2007-05-24 09:20:20

引用:原帖由 kuaizaifeng 于 2006-8-17 17:21 发表


为啥不让相加?? 


得到的是一个没有意义 的常数撒~~~

MAY BE ~:wink::wink:


 hehed 回复于:2007-05-24 13:59:37

只有指向同一连续存储单元的两个指针才可以进行减法运算,楼主的做法应该是不被允许的.
如果可以编译通过,得到的结果应该也不是一个定值.因为不确定这两个变量被分配到了什么地址里.如果得到"2"的结果,应该也只是凑巧.


 思一克 回复于:2007-05-24 14:09:52

LZ的程序怎么没有意义?

对齐与否在程序中根本没有体现出来(没有使用志向的内容)。

两个指针减GCC+就是做如下操作

 value = ((char*)p2 - (char*)p1) / sizeof(int);

其他什么也没有。因此总是有意义的。

你将指针看成普通数字就可以了。


 思一克 回复于:2007-05-24 14:29:07

当成普通数字看就可以了



main()
{
int *p1;
int *p2;
int value;

  p1 = (int *)0x500;
  p2 = (int *) 0x501;
  value = p2-p1;
  printf("value=%d \n",value);

  p1 = 3;
  p2 = 5;
  p2 = (int)p1 + (int)p2;
  printf("p2 = %d\n", p2);

  p1 = 5;
  p2 = 4;
  p2 = (int)p1*(int)p2;
  printf("p2 = %d\n", p2);

}





 beepbug 回复于:2007-05-24 15:51:41

在低档单片机开发中,譬如在C51上开发,可以把指针等同于地址。
在其它场合,把指针当作地址,可能会有麻烦。


 guojkd 回复于:2007-05-28 19:01:44

指向一段连续内存的字符串指针是可以加的吧

[ 本帖最后由 guojkd 于 2007-5-28 20:03 编辑 ]




原文链接:http://bbs.chinaunix.net/viewthread.php?tid=813131
转载请注明作者名及原文出处



收藏本页到: