且构网

分享程序员开发的那些事...
且构网 - 分享程序员编程开发的那些事

重新访问`void **':void * pop(void ** root)

更新时间:2023-11-30 23:42:22

[我在void **上的跟踪记录并不好。我相信这个回复是正确的,

但是它可能会为这个世界的Dan Pops做出丰富的选择,所以你可能想要等到周一(bn)在你开始思考之前,似乎不会在周末发布

IIRC。]


Stig Brautaset写道:
>
< snip>
[My track record on void ** is not good. I believe this reply to be correct,
but it might just make rich pickings for the Dan Pops of this world, so you
might want to wait until Monday (Dan doesn''t seem to post at the weekends
IIRC) before you make your mind up.]

Stig Brautaset wrote:

<snip>

/ * 1:如果在ANSI C * /
void * pop中允许这样做,则不是100%确定void ** root)
{struct node2 * p = * root;


从句法上讲,这很好。只要* root最初是一个

struct node2 *,它在语义上也很好。

如果(!p)
返回NULL;
* root = p-> next;


这也没关系 - 你将一个struct2 *分配给一个空*,这是合法的。

p-> next = NULL;
返回p;
}
int main(无效)
{


< snip>

/ * 2:GCC发出警告,除非我把那个演员投入。* /
while((p = pop((void **)& root))){

/* 1: not 100% certain if this is allowed in ANSI C */
void *pop(void **root)
{
struct node2 *p = *root;
This is fine, syntactically speaking. As long as *root was originally a
struct node2 *, it''s fine semantically, too.
if (!p)
return NULL;
*root = p->next;
This is okay too - you''re assigning a struct2 * to a void *, which is legal.
p->next = NULL;
return p;
}

int main(void)
{
<snip>
/* 2: GCC gives a warning unless I put that cast in. */
while ((p = pop((void **)&root))) {




这是否正确不太清楚。我认为打破它会更好,这意味着重新调整你的循环。通过分解,

我的意思是:


void * tmp = root;

p = pop(& tmp);


我没有看到gcc抱怨的任何理由。


-

Richard Heathfield: bi****@eton.powernet.co.uk

Usenet是一个奇怪的地方。 - Dennis M Ritchie,1999年7月29日。

C FAQ: http://www.eskimo.com/~scs/C-faq/top.html

K& R答案,C书等: http://users.powernet.co.uk/eton



Whether this is correct is less clear. I think it would be better to break
it up, which will mean restructuring your loop a little. By "break it up",
I mean:

void *tmp = root;
p = pop(&tmp);

I don''t see any reason for gcc to complain about that.

--
Richard Heathfield : bi****@eton.powernet.co.uk
"Usenet is a strange place." - Dennis M Ritchie, 29 July 1999.
C FAQ: http://www.eskimo.com/~scs/C-faq/top.html
K&R answers, C books, etc: http://users.powernet.co.uk/eton


2003年10月25日星期六14:39:30 +0000,Stig Brautaset写道:
On Sat, 25 Oct 2003 14:39:30 +0000, Stig Brautaset wrote:
弱点是pop()函数,需要指向指向根节点的指针。 c-faq状态(q:4.9)你可以_not_
依赖void **作为指针的通用指针。


常见问题是正确的。

然而,这篇文章(由Ben Pfaff在这一组中)似乎表明_are_用于` void
**''是可以接受的:

http:/ /tinyurl.com/sbem [0]


Ben Pfaff在那篇文章中的建议和你正在做的事情相当不同。他的void **是一个指向动态分配数组的指针

指向void的指针。在该数组中void的每个指针都将

用作通用指针。他的虚空**并没有被用作

泛型指针,而是被用来访问数组。

因此,我问:是否可以使用`我在这种情况下使用的是什么?
论证是我不会将它用作指向任何类型指针的指针,只能用于结构。


我不这么认为。我会试着解释一下。对于以下内容,假设

我们正在一台奇怪的机器上进行编译,其中

void *的表示与

struct的表示形式不同node1 *(或struct node2 *)并且编译器必须生成代码以从void *转换为struct node1 *或者将$
$ b $反之亦然。这是你对pop()的调用:

p = pop((void **)& root))


首先你拿指针指向struct node1的指针和force

编译器将其视为指向void的指针。

这种情况​​将其struct node1 **参数转换为void **,

必要时。但是,它不会转换root的表示形式

。它仍然是struct node1 *,而不是void *

/ * 1:如果在ANSI C中允许这样做,则不是100%确定* *
void * pop(void ** root)
{struct node2 * p = * root;


这里你的目的是将* root,你知道是一个

struct node1 *,分配给p。 C FAQ中描述的问题是:

此时编译器不知道* root实际上是一个struct node1 *

。它认为* root是一个空*。


编译器在为p赋值时必须进行转换

以便更改对于

struct node2 *的void *。它将生成执行转换的代码。

但是因为* root实际上不是空*,转换代码将无法正常运行并且值存储在p中不会是你想要的b $ b。

if(!p)
返回NULL;
* root = p-> next;
p-> next = NULL;
返回p;
}
The weak spot is the pop() function, that needs to take a pointer to a
pointer to the root node. The c-faq states (q: 4.9) that you can _not_
rely on void ** as a generic pointer to pointer.
The FAQ is correct.
However, this post (by
Ben Pfaff in this group) seem to indicate that there _are_ uses of `void
**'' that are acceptable:

http://tinyurl.com/sbem [0]
What Ben Pfaff suggests in that post and what you are doing are quite
different. His void ** is a pointer to a dynamically allocated array
of pointers to void. Each of the pointers to void in that array will
be used as a generic pointer. His void ** is not being used as a
generic pointer, rather it is being used to access the array.
Therefore, I ask: is it okay to use `void **'' for my use in this case?
The argument is that I wouldn''t be using it as a pointer to pointer to
any type but for structures only.
I don''t think so. I''ll try to explain. For the following, assume
we are compiling on a weird machine where the representation of
void * is incompatibly different than the representation of
struct node1 * (or struct node2 *) and that the compiler must
generate code to convert from void * to struct node1 * or vice-
versa. Here''s your call to pop():
p = pop((void **)&root))
First you take a pointer to pointer to struct node1 and force
the compiler to consider it a pointer to pointer to void.
This case converts its struct node1 ** argument to a void **,
as necessary. However, it does not convert the representation
of root. It remains a struct node1 *, and not a void *
/* 1: not 100% certain if this is allowed in ANSI C */
void *pop(void **root)
{
struct node2 *p = *root;
Here your intent is to assign *root, which you know is a
struct node1 *, to p. The problem described in the C FAQ is this:
The compiler does not know that *root is actually a struct node1 *
at this point. It thinks *root is a void *.

The compiler, when assigning a void * to p must do a conversion
in order to change the representation of a void * to that of a
struct node2 *. It will generate the code to do the conversion.
But since *root is not actually a void *, the conversion code will
not work properly and the value stored in p will not be what you
want.
if (!p)
return NULL;
*root = p->next;
p->next = NULL;
return p;
}




理查德的解决方案:


void * tmp = root;

p = pop(& tmp);


通过让编译器执行此操作来解决此问题在调用pop()之前正确转换

。但是我怀疑你没有想要要求你的用户做这件事(或者必须做空虚**

为此而投。) />

一种解决方案是声明一个列表类型,并让你的push()和pop()

函数采用该类型的参数:


(警告:未经测试的代码)


结构列表

{

struct node2 * root;

/ *你还可以包括其他有用的成员,如:* /

unsigned int size;

};


void init(struct list * plist)

{

plist-> root = NULL;

plist-> size = 0;

}


void push(struct list * plist,void * p)

{

struct node2 * q = p;

if(!q)return;


q-> next = plist-> root;

plist-> root = p;

plist-> size + = 1;

}


void * pop(struct list * plist)

{

struct node2 * p;

if(plist-> size == 0)返回NULL;


p = plist-> root;

plist - > root = plist-> root-> next;

p-> next = NULL;

返回p;

}


-Sheldon



Richard''s solution:

void *tmp = root;
p = pop(&tmp);

resolves this problem by getting the compiler to do this conversion
correctly before the call to pop(). However I suspect you don''t
want to require your users to do this (or to have to do the void **
cast for that matter).

One solution is to declare a list type, and have your push() and pop()
functions take a parameter of that type:

(warning: untested code)

struct list
{
struct node2 * root;
/* you can also include other useful members like: */
unsigned int size;
};

void init (struct list * plist)
{
plist->root = NULL;
plist->size = 0;
}

void push(struct list * plist, void * p)
{
struct node2 *q = p;
if (!q) return;

q->next = plist->root;
plist->root = p;
plist->size += 1;
}

void * pop(struct list * plist)
{
struct node2 * p;
if (plist->size == 0) return NULL;

p = plist->root;
plist->root = plist->root->next;
p->next = NULL;
return p;
}

-Sheldon


在文章< sl *********** ****** @ tuls.pauken.co.uk>

Stig Brautaset< ne ** @ brautaset.org>写道:
In article <sl*****************@tuls.pauken.co.uk>
Stig Brautaset <ne**@brautaset.org> writes:
...这篇文章底部的程序说明了我想做的事情:`struct node2'的声明只存在于
库中源文件。使用库时,我可以定义多个不同类型元素的列表。唯一的要求
是 - >下一个成员将是
结构中的第一个成员。

我不是百分之百确定它不会''但是,它依赖于未定义的行为。


确实如此。

弱点是pop()函数,需要指向指向根节点的指针。 c-faq状态(q:4.9)你可以_not_
依赖void **作为指针的通用指针。然而,这篇文章(由Ben Pfaff在这一组中)似乎表明_ad_使用了'void
**''是可以接受的:

http://tinyurl.com/sbem [0]

因此,我问:在这种情况下,我可以使用`void **'吗?


确实有void **的用途,但这不是其中之一。

什么是void **可以做的是指向类型为void *的实际对象。


有一种方法可以考虑这一点,通常应该让你

正确答案;我马上就会谈到这一点。

论证是我不会将它用作指向任何类型指针的指针,只能用于结构。


在至少一个实际实现中,void * (和char *)是

字节指针,而所有其他指针 - short *,int *,float *,

double *,并且重要的是,结构S *用于任何S。 - 是word

指针。

标准(C89,A6.8)规定任何指向对象的指针都可以转换为指向void *和back的指针。但是`void *''可以被认为是一个可以指向它的对象吗?


是的。但是你必须拥有这样一个对象的实际实例。


考虑这个问题的方法是:


- 演员总是会产生一个转换,好像是通过分配给临时的b $ ba。铸造和

普通任务之间的唯一真正区别(就是这样一个临时的)是

(a)如果你有一个实际的临时任务,你可以采取它的

地址,以及(b)涉及指针的强制转换可以删除dodgy中的

要求,通常是实际发布的诊断

消息案件。


- 来往或来自void *的普通转让变量也是

产生转换。


混合时获得的转换void *和其他指针

类型,至少在原则上,就像你在积分和浮点类型之间转换时得到的那些。在今天的

典型的32位整数32位IEEE-float机器上,如果你有:


int i;

浮动f;


然后sizeof i == sizeof f,但在类似的操作中:


i = f; / *或* /

f = i; / *为它们赋值后,当然* /


没人希望这些是简单的复制位在查看浮点数如何工作或查看编译器生成的实际机器代码之后,最不要考虑操作。 (机器

代码可能是单个将FP转换为int或转换为

到FP指令,甚至是一整套指令,取决于CPU上的
。)在任何情况下,从3.14159

(一个浮点数)到3(一个int)的变化实际上会改变内存中的位模式,

,以便存储在i中的比特的存储器转储。和那些存储在f中的
看起来没什么两样的。此外,做:


i = f,f = i;


甚至:


f =(int)f;


删除小数点后的任何数字,将f从3.14159

更改为3.0。显然,普通的分配不是按位复制!


这里的关键是,在C虚拟机中,由

标准定义,与指针同样发生。对于某些类型T,将指针

改为T,改为指向未指定字节的指针。 -

special" void *" C中的东西 - 当sizeof(T *)== sizeof(void *)时,允许更改位,甚至是
。在一些罕见的系统上,它确实会改变位数。 (在一些甚至更罕见的系统上,

sizeof()s甚至可能不同。)


因此,如果例程采用指向 ; void *"对象,你必须

传递一个实际的void *地址。对象 - 就像一个带有float *的

例程需要实际

浮点数的地址,而不是int转换为float *的地址。也就是说,

就像你一样:


result = set_a_float(& f);

i = f; / *我们只想要整数部分 - 做一个转换

来丢弃f的小数部分。 * /


你可以使用void *和其他指示:


struct S * x;

void * tmp;


result = set_a_voidstar(& ; tmp);

x = tmp; / *我们知道tmp中的值在转换后是有效的
(返回)到struct S *,所以转换也是如此。 * /

另一种方法是声明所有函数来取代/返回一个`struct
mynode *''(以及`struct mynode **''用于有问题的pop()
功能)。


还有其他选择(虽然就我而言,我认为

链表 - 无论是否作为堆栈使用 - 如此简单

你可以在许多情况下直接做它们。一个是上面概述的

:有一个类型为

" void *"的实际临时变量,传递其地址,并转换存储在其中的结果。 br />
另一种是返回一个包含两个void *的结构:


struct list {

struct list * next ;

};

struct uglyval {

void * head;

void * rest;

};


struct uglyval ugly(void * head){

struct popval ret;

struct list * tmp;


tmp = head;

ret.head = tmp; / *或ret.head = head * /

ret.rest = tmp-> next;

返回ret;

}


第二个版本说明了两个

方法的真正问题,因为现在堆栈弹出序列来自:

while(( p = pop((void **)& root))){
printf(" p-> val:%d \ n",p-> val);
免费( p);
}
... The program at the bottom of this post illustrates what
I want to do: the declaration of `struct node2'' would exist only in the
library source file. When using the library I would be able to define
multiple lists of different types of elements. The only requirement
would be that the ->next member would be the first member in the
structure.

I''m not 100% certain that it doesn''t rely on undefined behaviour though.
It does.
The weak spot is the pop() function, that needs to take a pointer to a
pointer to the root node. The c-faq states (q: 4.9) that you can _not_
rely on void ** as a generic pointer to pointer. However, this post (by
Ben Pfaff in this group) seem to indicate that there _are_ uses of `void
**'' that are acceptable:

http://tinyurl.com/sbem [0]

Therefore, I ask: is it okay to use `void **'' for my use in this case?
There are indeed uses for "void **", but this is not one of them.
What "void **" can do is point to actual objects of type "void *".

There is a way to think about this that should generally get you
the right answer; I will get to that in a moment.
The argument is that I wouldn''t be using it as a pointer to pointer to
any type but for structures only.
In at least one actual implementation, "void *" (and "char *") are
"byte pointers", while all other pointers -- short *, int *, float *,
double *, and importantly, struct S * for any "S" -- are "word
pointers".
The standard (C89, A6.8) states that any pointer to an object can be
converted to a pointer to void * and back. But can `void *'' be thought
of as an object on its own which can be pointer to?
Yes. But you must have an actual instance of such an object.

The way to think about this is:

- A cast always produces a conversion, as if by assignment to
a temporary. The only real differences between casting and
ordinary assignment (to just such a temporary) are that
(a) if you had an actual temporary, you could take its
address, and (b) casts involving pointers can remove the
requirement for, and usually the actual issuing of, diagnostic
messages in "dodgy cases".

- Ordinary assignment to or from "void *" variables also
produces a conversion.

The conversions you get when mixing "void *" and other pointer
types are, at least in principle, just like those you get when
converting between integral and floating-point types. On today''s
typical 32-bit-integer 32-bit-IEEE-float machine, if you have:

int i;
float f;

then sizeof i == sizeof f, yet in operations like:

i = f; /* or */
f = i; /* after assigning values to them, of course */

nobody expects these to be simple "copy the bits" operations, at
least not after examining how floating-point works or looking at
the actual machine code produced by the compiler. (The machine
code involved may be a single "convert FP to int" or "convert int
to FP" instruction, or even a whole series of instructions, depending
on the CPU.) In any case, the change from something like 3.14159
(a float) to 3 (an int) actually changes the bit pattern in memory,
so that a memory-dump of the bits stored in "i" and those stored
in "f" look nothing alike. Moreover, doing:

i = f, f = i;

or even:

f = (int)f;

drops any digits past the decimal point, changing f from 3.14159
to 3.0. Clearly ordinary assignment is not a bitwise copy!

The key here is that, in the "C virtual machine" defined by the
standard, THE SAME THING HAPPENS WITH POINTERS. Changing "pointer
to T", for some type T, to "pointer to unspecified byte(s)" -- the
special "void *" thing in C -- is allowed to change the bits, even
when sizeof(T *) == sizeof(void *). On a few rare systems, it
really does change the bits. (On a few even-rarer systems, the
sizeof()s may even differ.)

Thus, if a routine takes a pointer to a "void *" object, you must
pass it the address of an actual "void *" object -- just as a
routine that takes a "float *" needs the address of an actual
float, not the address of an int cast to "float *". That is,
just as you would do:

result = set_a_float(&f);
i = f; /* we only want the integer part -- do a conversion
to discard the fractional part of f. */

you can do the equivalent with "void *" and other pointers:

struct S *x;
void *tmp;

result = set_a_voidstar(&tmp);
x = tmp; /* we know that the value in tmp is valid after conversion
(back) to "struct S *", so do the conversion. */
The alternative is to declare all functions to take/return a `struct
mynode *'' instead (and `struct mynode **'' for the problematic pop()
function).
There are other alternatives (although for my part, I think of
linked lists -- whether used as stacks or not -- as so simple that
you might as well just do them in line in many cases). One is the
one outlined above: have an actual temporary variable of type
"void *", pass its address, and convert the result stored in it.
Another is to return a structure containing two "void *"s:

struct list {
struct list *next;
};
struct uglyval {
void *head;
void *rest;
};

struct uglyval ugly(void *head) {
struct popval ret;
struct list *tmp;

tmp = head;
ret.head = tmp; /* or ret.head = head */
ret.rest = tmp->next;
return ret;
}

This second version illustrates the real problem with both
approaches, because now the stack-pop sequence goes from:
while ((p = pop((void **)&root))) {
printf("p->val: %d\n", p->val);
free(p);
}




(其中p和root都是struct node1 *)到:


struct node1 * p,* root;

struct uglyval tmp;

...

for( ;;){

tmp =丑陋(根);

if((p = tmp.head)== NULL)

break;

root = tmp.rest;

printf(" p-> val:%d \ n",p-> val);

免费(p);

}


你也可以只是内联整个内容,给予:
>
while(root!= NULL){

p = root;

root = p-> next;

...

}


摆脱临时和功能丑陋()。带有单个void *的

版本临时不是很糟糕:


struct node1 * p,* root;

void * tmp;

...

while(tmp = root,p = pop(& tmp),root = tmp,p!= NULL){

...

}


这只是丑陋()循环的逗号运算符化变体。

我们可以用丑陋()来做同样的事情:


while(tmp = ugly(root),p = tmp.head,root = tmp.rest,p!= NULL)


但结果仍然是,丑陋。 :-)


最后一种技术使用所有结构指针

闻起来相同 comp.std.c人们向我们保证,这个想法甚至是真实的,尽管它在C标准中没有明确规定。这个

的方法非常糟糕,以至于我决定在

之后不再说明。

-

In - 实际生活:风河系统Chris Torek

美国犹他州盐湖城(40°39.22''N,111°50.29''W)+1 801 277 2603

电子邮件:忘了它 http://67.40.109.61/torek/index .html (目前)

由于垃圾邮件发送者,阅读电子邮件就像在垃圾中搜索食物一样。



(where "p" and "root" are both "struct node1 *") to:

struct node1 *p, *root;
struct uglyval tmp;
...
for (;;) {
tmp = ugly(root);
if ((p = tmp.head) == NULL)
break;
root = tmp.rest;
printf("p->val: %d\n", p->val);
free(p);
}

and you might as well just inline the whole thing, giving:

while (root != NULL) {
p = root;
root = p->next;
...
}

getting rid of both the temporary and function ugly(). The
version with a single "void *" temporary is not QUITE as bad:

struct node1 *p, *root;
void *tmp;
...
while (tmp = root, p = pop(&tmp), root = tmp, p != NULL) {
...
}

which is just a comma-operator-ized variant of the ugly() loop.
We can do the same with ugly():

while (tmp = ugly(root), p = tmp.head, root = tmp.rest, p != NULL)

but the result remains, well, ugly. :-)

There is one last technique that uses the "all structure pointers
smell the same" idea that, comp.std.c folks assure us, is true even
though it is not literally spelled out in the C standard. This
method is horrible enough that I decided not to illustrate it after
all.
--
In-Real-Life: Chris Torek, Wind River Systems
Salt Lake City, UT, USA (40°39.22''N, 111°50.29''W) +1 801 277 2603
email: forget about it http://67.40.109.61/torek/index.html (for the moment)
Reading email is like searching for food in the garbage, thanks to spammers.