Chinaunix

标题: 这个最基础的注入为什么会出错啊? [打印本页]

作者: iLRainyday    时间: 2009-12-14 18:18
标题: 这个最基础的注入为什么会出错啊?
模仿着内核版的一个精华帖,写了一个最基础的练习题,不知道为什么会重复输出两次,然后segment fault

#include <stdio.h>

void attack() {
&nbsp;&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;&nbsp;printf("hi,attacked!\n");
}


void foo() {
&nbsp;&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;&nbsp;int c_foo;
&nbsp;&nbsp;&nbsp;&nbsp;int main_eip = (int) *(&c_foo + 3);
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;&nbsp;*(&c_foo + 3) = (int)attack;
&nbsp;&nbsp;&nbsp;&nbsp;*(&c_foo + 4) = main_eip;
&nbsp;&nbsp;&nbsp;&nbsp;
}

void main()
{
&nbsp;&nbsp;int local;
&nbsp;&nbsp;foo();
&nbsp;&nbsp;printf("I am retrun!\n");
&nbsp;
}

作者: cjaizss    时间: 2009-12-14 18:53
呵呵,还是建议用汇编写
作者: nicolas.shen    时间: 2009-12-14 19:43
int main_eip = (int) *(&c_foo + 2);   
    *(&c_foo + 2) = (int)attack;
    *(&c_foo + 3) = main_eip;
Solaris下面这样是可以的,我猜你想要这样的结果吧
作者: iLRainyday    时间: 2009-12-14 20:02
用gdb逐步跟踪了,地址计算方面没有错误,不过很有意思的是在foo()中int main_eip的位置是在int  c_foo之前的。然后正常的进入attack(),返回main(),执行printf(),之后就出问题了,进入了一个好像是什么in_start()之类的函数,连续的next之后,就莫名其妙的回到了foo()中...
作者: mik    时间: 2009-12-14 22:11
原帖由 iLRainyday 于 2009-12-14 18:18 发表
模仿着内核版的一个精华帖,写了一个最基础的练习题,不知道为什么会重复输出两次,然后segment fault

#include

void attack() {
&nbsp;&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;& ...


汗,你把那篇东西找出来对一对,连抄都抄错

还是要理解才好呀。
作者: cjaizss    时间: 2009-12-14 22:29
画个图,来理解进栈出栈,就可以加深理解。这里的原理并不复杂,更不高深,用汇编再对着图,很快就可以写出预计的结果。
作者: iLRainyday    时间: 2009-12-14 22:53
汗,你把那篇东西找出来对一对,连抄都抄错

还是要理解才好呀。


我没有照抄啦~~  按自己理解来计算,就是返回到main()之后出错了,call foo之后是一个add $xxx, %esp,我发现每次我返回到这里的时候,%esp的值总是和正确的值差了4个字节,估计就是这个原因造成连续输出两句“hi,attacked!” 按照mik老大的意思,由attack()来处理返回,可是返回的时候还是%esp的问题
作者: mik    时间: 2009-12-14 23:03
你想:main() --> foo() ---->  attack() ---> main()     是吧?

如果要在 foo() 里设的话就要:

foo() 的返回值置为 attack()

attack() 的返回值置为 main()

[ 本帖最后由 mik 于 2009-12-15 00:23 编辑 ]
作者: iLRainyday    时间: 2009-12-14 23:25
我画了个图,mik老大看看我哪里理解错了

cu.jpg (43.67 KB, 下载次数: 80)

cu.jpg

作者: mik    时间: 2009-12-14 23:51
原帖由 iLRainyday 于 2009-12-14 23:25 发表
我画了个图,mik老大看看我哪里理解错了



由于在函数里:

push ebp                    <----- 建栈
mov ebp, esp
...
...
mov esp, ebp
pop ebp                        <----- 销栈
ret

------------------------------------

所以,在执行 attack() 时,也需要遵循这个原则

因此,还要为 attack() 函数留了一个 [ebp] 值

----------------------------
ret_value                                    <--------------------- main() 地址
----------------------------
ebp
---------------------------
ret_value                                     <-------------------  attack() 地址
---------------------------
ebp
---------------------------
main_eip
---------------------------
c_foo
----------------------------

[ 本帖最后由 mik 于 2009-12-15 00:21 编辑 ]
作者: iLRainyday    时间: 2009-12-15 02:04
按照mik老大的指点,从foo()中ret后,应该是这样的:

----------------------------
ret_value                                    <--------------------- main() 地址
----------------------------
%ebp
---------------------------

下面是attack():
push %ebp
mov %esp, %ebp
sub $0x8, %esp
....
leave
ret

也就是说attack()自己还会压入一个%ebp:

----------------------------
ret_value                                    <--------------------- main() 地址
----------------------------
%ebp
---------------------------
%ebp
---------------------------

那么当执行leave时,把pop出最顶上的%ebp,然后ret的时候....貌似再也回不去了(segment fault)

我已经崩溃了~~~~
作者: OwnWaterloo    时间: 2009-12-15 04:55
标题: 这个好玩
lz的代码造成segment fault可能是因为堆栈没有平衡。 实际上是多出栈了一次。
修改了foo的返回地址, 使得foo返回到attack而非main。
当foo返回到attack时, 再加上最终会在main中执行的foo调用的epilogue, 堆栈已经平衡。
而attack本身也会返回, 就多返回一次, 多出栈一次, 堆栈就完蛋了。


我也来一段吧
#include <stdio.h>

void g() { printf("call g\n"); }

void epilogue(void) {}

void prologue(void* anchor)
{
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;void** top = &anchor - 1;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;void** end = anchor;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;top[0] = g;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;while ( ++top != end )
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;*top = epilogue;
}

void f(void* anchor)
{
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;void (*noinline)(void*) = prologue;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;noinline(&anchor - 1);
}

int main(void)
{
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;void (*noinline)(void* ) = f;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;printf("enter\n");

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;noinline(0);

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;printf("leave\n");
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return 0;
}



prologue:
1. 将prologue的返回地址填为g, 使得prologue返回后,会执行g。
2. 在返回之前,将prologue的返回地址与f的返回地址(不包含f的返回地址)之间填为epilogue。
3. "返回到g"后, g的返回地址就是epilogue。

epilogue :
1. 在epilogue 中一直返回到自身, 用于平衡堆栈。
2. 重复1 直到最终返回到f的返回地址。 f调用完毕。

mingw 3.4.5
是否使用-O3 -fomit-frame-pointer都没有关系。

msvc
release没有问题。
debug会开启堆栈帧检查/RTCs。 虽然,其实堆栈是正确平衡了的。
f 中有将esp保存到esi的代码, 但恢复esi的代码, 却因为prologue函数不再返回f而得不到执行。 epilogue 返回main后,esp的值是调用前的值,但esi没有被正确回复。
去掉/RTCs, 会在代码最后形成一个奇怪的无限循环, 原因不明……
作者: OwnWaterloo    时间: 2009-12-15 04:59
不过……  上面的代码, 以及下面的代码:

原帖由 iLRainyday 于 2009-12-8 22:34 发表
...
#define ARRAY_SIZE(arr)
   (sizeof(arr) / sizeof(((typeof(arr)){})[0]))
...


除了好玩, 真能派上什么用场???
作者: mik    时间: 2009-12-15 07:57
原帖由 iLRainyday 于 2009-12-15 02:04 发表
按照mik老大的指点,从foo()中ret后,应该是这样的:
----------------------------
ret_value                                    

应该没错的,试过没?

[ 本帖最后由 mik 于 2009-12-15 08:04 编辑 ]
作者: iLRainyday    时间: 2009-12-15 11:05
GOD!!!Jesus Christ!!!!这代码也太绕了吧,本来推算堆栈的位置就够费神了,这个连二级指针都能出来了。不过通过参数来确定eip的位置的方法不错,比起用局部变量似乎要清晰。

不过发现两件事情:
1. 如果有 void *ptr_v,肯定是不能对ptr_v进行算术运算,因为没有指向的type信息。但是竟然可以对&ptr_v进行算术运算,而且+1的步长和int *ptr_i是一样的

2.在prologue中,进行
  
while ( ++top != end )
            *top = epilogue;

的时候,会将f()用于返回时的ebp给覆盖掉,这样子没有问题吗?

另外,#define ARRAY_SIZE(arr) \
   (sizeof(arr) / sizeof(((typeof(arr)){})[0])) 就是为了防止不小心将指针作为参数传入,别的貌似也没了
作者: iLRainyday    时间: 2009-12-15 11:06
应该没错的,试过没?

老大,这个试过的,真的不行
作者: 学与思    时间: 2009-12-15 15:28
把汇编代码贴出来才能看出问题
作者: iLRainyday    时间: 2009-12-15 15:44
按照老大的指点该成这样子了:


#include <stdio.h>

void attack() {
&nbsp;&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;printf("hi,attacked!n");
&nbsp;&nbsp;
}


void foo() {
&nbsp;&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;&nbsp;int c_foo;
&nbsp;&nbsp;&nbsp;&nbsp;int main_eip = (int) *(&c_foo + 3);
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;&nbsp;*(&c_foo + 3) = (int)attack;
&nbsp;&nbsp;&nbsp;&nbsp;*(&c_foo + 4) = (int) *(&c_foo + 2);
&nbsp;&nbsp;&nbsp;&nbsp;*(&c_foo + 5) = main_eip;
&nbsp;&nbsp;&nbsp;&nbsp;
}

void main()
{
&nbsp;&nbsp;int var;
&nbsp;&nbsp;foo();
&nbsp;&nbsp;printf("I am retrun!n");
&nbsp;
}

作者: 学与思    时间: 2009-12-15 15:50
那对了没
作者: iLRainyday    时间: 2009-12-15 16:28
那对了没

没有呀,还是segment fault
如果不觉得麻烦的话,您可以编译一下试试
作者: 学与思    时间: 2009-12-15 16:33
这个跟gcc的版本有很大关系,也不知道你用的哪个gcc, 你最好把main的汇编代码贴出来
你应该知道如何反汇编吧,在gdb中disass main
作者: iLRainyday    时间: 2009-12-15 16:46
gcc (GCC) 4.1.2 20080704 (Red Hat 4.1.2-44)
Copyright (C) 2006 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

(gdb) disassemble main
Dump of assembler code for function main:
0x080483d3 <main+0>:    lea    0x4(%esp),%ecx
0x080483d7 <main+4>:    and    $0xfffffff0,%esp
0x080483da <main+7>:    pushl  -0x4(%ecx)
0x080483dd <main+10>:   push   %ebp
0x080483de <main+11>:   mov    %esp,%ebp
0x080483e0 <main+13>:   push   %ecx
0x080483e1 <main+14>:   sub    $0x14,%esp
0x080483e4 <main+17>:   call   0x8048398 <foo>
0x080483e9 <main+22>:   movl   $0x80484dd,(%esp)
0x080483f0 <main+29>:   call   0x8048294 <puts@plt>
0x080483f5 <main+34>:   add    $0x14,%esp
0x080483f8 <main+37>:   pop    %ecx
0x080483f9 <main+38>:   pop    %ebp
0x080483fa <main+39>:   lea    -0x4(%ecx),%esp
0x080483fd <main+42>:   ret   
End of assembler dump.

(gdb) disassemble foo
Dump of assembler code for function foo:
0x08048398 <foo+0>:     push   %ebp
0x08048399 <foo+1>:     mov    %esp,%ebp
0x0804839b <foo+3>:     sub    $0x10,%esp
0x0804839e <foo+6>:     lea    -0x8(%ebp),%eax
0x080483a1 <foo+9>:     add    $0xc,%eax
0x080483a4 <foo+12>:    mov    (%eax),%eax
0x080483a6 <foo+14>:    mov    %eax,-0x4(%ebp)
0x080483a9 <foo+17>:    lea    -0x8(%ebp),%eax
0x080483ac <foo+20>:    add    $0xc,%eax
0x080483af <foo+23>:    mov    $0x8048384,%edx
0x080483b4 <foo+28>:    mov    %edx,(%eax)
0x080483b6 <foo+30>:    lea    -0x8(%ebp),%edx
0x080483b9 <foo+33>:    add    $0x10,%edx
0x080483bc <foo+36>:    lea    -0x8(%ebp),%eax
0x080483bf <foo+39>:    add    $0x8,%eax
0x080483c2 <foo+42>:    mov    (%eax),%eax
0x080483c4 <foo+44>:    mov    %eax,(%edx)
0x080483c6 <foo+46>:    lea    -0x8(%ebp),%eax
0x080483c9 <foo+49>:    add    $0x14,%eax
0x080483cc <foo+52>:    mov    -0x4(%ebp),%edx
0x080483cf <foo+55>:    mov    %edx,(%eax)
0x080483d1 <foo+57>:    leave  
0x080483d2 <foo+58>:    ret   
End of assembler dump.

(gdb) disassemble attack
Dump of assembler code for function attack:
0x08048384 <attack+0>:  push   %ebp
0x08048385 <attack+1>:  mov    %esp,%ebp
0x08048387 <attack+3>:  sub    $0x8,%esp
0x0804838a <attack+6>:  movl   $0x80484d0,(%esp)
0x08048391 <attack+13>: call   0x8048294 <puts@plt>
0x08048396 <attack+18>: leave  
0x08048397 <attack+19>: ret   
End of assembler dump.

作者: 学与思    时间: 2009-12-15 16:53
0x080483f5 <main+34>:   add    $0x14,%esp
0x080483f8 <main+37>:   pop    %ecx

关键应该在这里,你只call了一次,也就是eip压入栈只进行了一次,却两个函数的最后的ret却会pop eip两次,所以main中堆栈不平衡了,估计你在主函数的printf之前或则main返回之前加一句__asm__("sub $0x4,%esp"); 就对了
作者: OwnWaterloo    时间: 2009-12-15 16:54
以i386来说 :

原帖由 iLRainyday 于 2009-12-15 11:05 发表
GOD!!!Jesus Christ!!!!这代码也太绕了吧,本来推算堆栈的位置就够费神了,这个连二级指针都能出来了。不过通过参数来确定eip的位置的方法不错,比起用局部变量似乎要清晰。

你无法确定局部变量和返回地址之间的偏移。 编译器可能会在局部变量之间放入一些填充, 进行一些错误检查。
是否有frame-pointer也会影响这个偏移。

而anchor 和返回地址之间的的偏移就更稳定一些。 我也不知道会不会有例外情况。 但至少在gcc和msvc下是这样的。


原帖由 iLRainyday 于 2009-12-15 11:05 发表
1. 如果有 void *ptr_v,肯定是不能对ptr_v进行算术运算,因为没有指向的type信息。但是竟然可以对&ptr_v进行算术运算,而且+1的步长和int *ptr_i是一样的

不要被2级指针迷惑了。

void f( T anchor ) {
      // &anchor  是 T*
      // &anchor - 1 的步长当然就是是sizeof( T )。
}

将T代换为 void*  即可。 步长当然就是 sizeof( void*) , 在i386下当然和 sizeof(int*), sizeof(int) 是相同的。

取T=void* ,主要是为了少写一些显示类型转换。


原帖由 iLRainyday 于 2009-12-15 11:05 发表
会将f()用于返回时的ebp给覆盖掉,这样子没有问题吗?


假设, 开始执行prologue时, 堆栈如此

...
frame  of prologue
...
return of prologue   替换为g
anchor of prologue   替换为epilogue
...                  ... 替换为epilogue
frame  of f          替换为epilogue
...                  ... 替换为epilogue
return of f          维持
anchor of f


确实将f的frame全部破换了。 但只要g中不使用frame of f, 就应该不会有什么问题。


原帖由 iLRainyday 于 2009-12-15 11:05 发表
ARRAY_SIZE 就是为了防止不小心将指针作为参数传入,别的貌似也没了


将指针传递给ARRAY_SIZE, 就应该去回炉重学。

另外, 如果真的将指针传入这个使用了gcc扩展的ARRAY_SIZE, 会怎样? 编译错误?
难道会得到正确的结果???
作者: 学与思    时间: 2009-12-15 16:54
顺便说一句,用你最先那个版本哈,不需要保存ebp
作者: iLRainyday    时间: 2009-12-15 16:59
另外, 如果真的将指针传入这个使用了gcc扩展的ARRAY_SIZE, 会怎样? 编译错误?
难道会得到正确的结果???

的确是编译错误,这样子就能提醒编写者注意到问题,因为误传指针往往是无意之为(假设是个不熟练的程序员),这样子能减少后期的出错麻烦,作者应该是这么考虑的吧:wink:


关键应该在这里,你只call了一次,也就是eip压入栈只进行了一次,却两个函数的最后的ret却会pop eip两次,所以main中堆栈不平衡了,估计你在主函数的printf之前或则main返回之前加一句__asm__("sub $0x4,%esp"); 就对了

这个我今天早上也想到了,我一直觉得段错误是因为返回main()的时候退栈不平衡,因为attack()多pop了4字节,所以我在main()中的foo()之后,printf()之间加上了__asm__("sub $0x4,%esp");结果照样是segment fault.....


顺便说一句,用你最先那个版本哈,不需要保存ebp

以前那个版本也不行...同样的段错误...你现在知道我有多崩溃了吧....

[ 本帖最后由 iLRainyday 于 2009-12-15 17:03 编辑 ]
作者: 学与思    时间: 2009-12-15 17:02
我试一下
作者: iLRainyday    时间: 2009-12-15 17:12
哈哈~~~没问题了~~~我想起来了,今天早上我是把那句__asm__加到保存ebp那个版本里了,我刚才把它加到我最初那个版本里面,就ok了~

谢谢"mik,OwnWaterloo ,学与思"不厌其烦的指点!

#include <stdio.h>

void attack() {

&nbsp;&nbsp;printf("hi,attacked!n");
}


void foo() {

&nbsp;&nbsp;&nbsp;&nbsp;int c_foo;
&nbsp;&nbsp;&nbsp;&nbsp;int main_eip = (int) *(&c_foo + 3);

&nbsp;&nbsp;&nbsp;&nbsp;*(&c_foo + 3) = (int)attack;
&nbsp;&nbsp;&nbsp;&nbsp;*(&c_foo + 4) = main_eip;

}

void main()
{
&nbsp;&nbsp;int var;
&nbsp;&nbsp;foo();
&nbsp;&nbsp;__asm__("sub $0x4,%esp");
&nbsp;&nbsp;printf("I am retrun!n");

}


[ 本帖最后由 iLRainyday 于 2009-12-15 17:14 编辑 ]
作者: 学与思    时间: 2009-12-15 17:29
#include <stdio.h>

void attack() {
&nbsp;&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;&nbsp;printf("hi,attacked!\n");
}


void foo() {
&nbsp;&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;&nbsp;int c_foo;
&nbsp;&nbsp;&nbsp;&nbsp;int main_eip = (int) *(&c_foo + 3);
&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;&nbsp;*(&c_foo + 3) = (int)attack;
&nbsp;&nbsp;&nbsp;&nbsp;*(&c_foo + 4) = main_eip;
&nbsp;&nbsp;&nbsp;&nbsp;
}

int  main(int argc, char *argv[])
{
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;int local;

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;foo();
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;__asm__("sub $0x4,%esp");
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;printf("I am retrun!\n");

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return 0;
}

作者: iLRainyday    时间: 2009-12-15 17:30
将T代换为 void*  即可。 步长当然就是 sizeof( void*) , 在i386下当然和 sizeof(int*), sizeof(int) 是相同的。


不止是在这里,发现好像在C里,对于void *类型的pointer的地址做算术运算的时候,统一是按照4字节步进的。

#include <stdio.h>

int main()
{
&nbsp;&nbsp;&nbsp;&nbsp;int i;
&nbsp;&nbsp;&nbsp;&nbsp;char c;
&nbsp;&nbsp;&nbsp;&nbsp;void *ptr = NULL;

&nbsp;&nbsp;&nbsp;&nbsp;printf("&ptr is %#x, &ptr+1 is %#xn", &ptr, &ptr+1);

&nbsp;&nbsp;&nbsp;&nbsp;ptr = &i;
&nbsp;&nbsp;&nbsp;&nbsp;printf("&ptr is %#x, &ptr+1 is %#xn", &ptr, &ptr+1);

&nbsp;&nbsp;&nbsp;&nbsp;ptr = &c;
&nbsp;&nbsp;&nbsp;&nbsp;printf("&ptr is %#x, &ptr+1 is %#xn", &ptr, &ptr+1);

&nbsp;&nbsp;&nbsp;&nbsp;printf("&i is %#x, &i+1 is %#xn", &i, &i+1);
&nbsp;&nbsp;&nbsp;&nbsp;printf("&c is %#x, &c+1 is %#xn", &c, &c+1);

&nbsp;&nbsp;&nbsp;&nbsp;return 0;
}

2009-12-15 17 29 57.jpg (14.48 KB, 下载次数: 16)

2009-12-15 17 29 57.jpg

作者: OwnWaterloo    时间: 2009-12-15 17:30
标题: 回复 #28 iLRainyday 的帖子
我写那段代码时, 捣鼓了半天, 弄出一对prologue,epilogue 就是为了避免这一句 :
__asm__("sub $0x4,%esp");
作者: OwnWaterloo    时间: 2009-12-15 17:32
标题: 回复 #30 iLRainyday 的帖子
ptr + 1 和 &ptr + 1 是有区别的。

只要ptr 是左值, 无论其类型, &ptr + 1 都是可以的。
而ptr + 1 不一定。
作者: iLRainyday    时间: 2009-12-15 17:49
我写那段代码时, 捣鼓了半天, 弄出一对prologue,epilogue 就是为了避免这一句 :
__asm__("sub $0x4,%esp");


我那个是很丑陋笨拙的法子~~你的prologue &epilogue很优雅的,而且我也学习到了如何利用参数在堆栈中进行定位 :)

我之前一直以为对于没有指向类型的void *ptr进行&ptr+1操作时,是简单的指向下一个字节,所以刚开始看你的例子时一直没搞清楚堆栈定位,后来用gdb跟踪了一下才发现这个问题,我一会翻翻那本 C: A reference manual,看看上面有没有解释这个问题。

[ 本帖最后由 iLRainyday 于 2009-12-15 17:53 编辑 ]
作者: iLRainyday    时间: 2009-12-15 18:30
找到了一些说法:

在Generic Pointers一节中,“Type void * is considered to be neither an object pointer nor a function pointer"。在Address Operator一节中:”The operand of & must be either a funciton designator or an lvalue designating an object".

所以,如果有void * ptr时,如果进行&ptr,那么“似乎"是将ptr由void *转换成了int *. 实验如下:

int i = 1;
void *ptr;
ptr = &i;
i = *ptr;  --------->报错!
i = *(&ptr);  ----------->没问题!
作者: OwnWaterloo    时间: 2009-12-15 18:48
标题: 回复 #34 iLRainyday 的帖子
关于2级指针,我想, 我在24楼已经解释清楚了。


int i = 1;
void *ptr;
ptr = &i;
i = *ptr;  --------->报错!  当然,ptr是void*, 未知类型指针, 无法解引用。
i = *(&ptr);  ----------->没问题! 这里没问题是这样的:

(&ptr);  该表达式类型是  void**  是一个指针, 指向void* —— 不再是未知类型指针, 指向类型是已知的,void*。
*(&ptr); 所以该表达式可以解引用, 得到的类型是void* , 值就是ptr的值。
i = *(&ptr) ;  这里能通过, 是因为 void* (值是ptr) 被隐式转换为int了。



请教一下:
在Generic Pointers一节中,“Type void * is considered to be neither an object pointer nor a function pointer"

这个说法是在哪找到的? C: A reference manual ?
这个说法和C标准好像有点冲突。
作者: iLRainyday    时间: 2009-12-15 19:03
恩~~我似乎明白你的意思了~~

这个说法是在哪找到的? C: A reference manual ?
这个说法和C标准好像有点冲突。


在C: A reference manual一书中,第137页,5.3.2节
作者: OwnWaterloo    时间: 2009-12-15 19:21
标题: 回复 #36 iLRainyday 的帖子
能不能发我一下电子版?  多谢多谢~
或者, 书中在解释void* 可以指向函数时, 有没有说引用了标准哪一段?


印象中, 我没能在标准里找到说void*和函数指针有相同表示。
the c programming language也只是说void* 可以保存任何数据指针, 转换为原来类型不会丢失信息;不同类型的函数指针可以相互转换,转换为原来的类型不丢失信息。 但没有说数据指针和函数指针之间可以相互转换。

我上面的代码也仅仅是针对i386写的, void*, int, void (*)(void) 都是一回事。


而C: A reference manual, 不会仅仅针对i386吧? 所以想查证一下是否void* 可以保存函数指针。
作者: OwnWaterloo    时间: 2009-12-15 19:34
标题: 回复 #36 iLRainyday 的帖子
是《C A Reference Manual》?
作者是Samuel P. Harbison III和 Guy L. Steele Jr. ?


就在这一句:
Type void * is considered to be neither an object pointer nor a function pointer.


之前,就是另外一句意思不同的:
...
Any pointer to an object or incomplete type( but not a function type ) can be converted to type void* and back without change.
Type void * is considered to be neither an object pointer nor a function pointer.
...

—— 5.3.1 Generic Pointers, p137 末尾


费解……


补充:我引用的是第5版
作者: OwnWaterloo    时间: 2009-12-15 19:38
标题: 回复 #36 iLRainyday 的帖子
我发现错在哪了……
把neither nor的意思理解反了
作者: iLRainyday    时间: 2009-12-15 20:44
就是你说的那本,作者是Samuel P. Harbison III和 Guy L. Steele Jr. 这本书我一直没在网上见到过电子版。我手边是人邮03年出版的影印版(第五版)。

在p140页的5.3.3节最后一句说了: in standard C,void * can be used as a generic object pointer,but there is no generic funciton pointer.
还有一句是:function pointers and data pointers may have significantly differnet representations,including different sizes.

[ 本帖最后由 iLRainyday 于 2009-12-15 20:45 编辑 ]
作者: OwnWaterloo    时间: 2009-12-15 20:51
标题: 回复 #40 iLRainyday 的帖子
我也不知道在哪下到的电子版 ……  可能到处收刮的

function pointers and data pointers may have significantly differnet representations,including different sizes.

嗯, 这就和我以前理解的相同了。

in standard C,void * can be used as a generic object pointer,but there is no generic funciton pointer.

这里好像不对。


c89 6.3.4 Cast operators p45
...
A pointer to a function of one type may be converted to a pointer to a function of another
type and back again; the result shall compare equal to the original pointer. If a converted
pointer is used to call a function that has a type that is not compatible with the type of the
called function. the behavior is undefined.
...

也就是说, 随便哪一个pointer to function都可以作为generic function pointer,只要在使用前转型为原有的类型就没有问题。
作者: OwnWaterloo    时间: 2009-12-15 21:01
标题: 回复 #40 iLRainyday 的帖子
http://www.itpub.net/thread-1156870-1-5.html


刚才搜我硬盘时,发现还有另外2本……

F8C35AF1A56F14D01CAF00FD7FDCE084|C A Reference Manual, Fifth Edition.pdf
http://www.itpub.net/thread-453450-1-1.html
好像是SGI的

D057BC0083952B183BCB6CA8572519AB|C+Reference+Manual.pdf
http://d.download.csdn.net/down/237916/tzp_1210
by Dennis M. Ritchie


名字都很像……  但好像3本都没有什么联系……
好像也确实是Steele那本最出名。
作者: WTO432    时间: 2010-01-17 19:14
致富之路。。。




欢迎光临 Chinaunix (http://bbs.chinaunix.net/) Powered by Discuz! X3.2