Why pointers to pointers used

  • Thread starter ArifulHossain tuhin
  • Start date
A

ArifulHossain tuhin

I have used many OSS libraries which have a common structure like the following:

They have init function which takes a pointer to pointer to the module data structure like this:
module_init(struct module_ds ** p,.....);
And usage example is like following:

struct module_ds * p;
p = NULL;
module_init(&p,....);
etc.

Now i'm wondering why they do it this way? if i pass a plain pointer then it should be ok to declare a local pointer and allocate and initialize it. after initialization it can be returned by the fuction.

but they always have a void type.

Even if they remain "void" still it should be possible to allocate, and initialize a local pointer and assign it to a plain pointer comes as a argument?

Or "local" pointer is key here?

Thanks in advance.
 
B

Ben Bacarisse

ArifulHossain tuhin said:
I have used many OSS libraries which have a common structure like the
following:

They have init function which takes a pointer to pointer to the module
data structure like this:
module_init(struct module_ds ** p,.....);
And usage example is like following:

struct module_ds * p;
p = NULL;
module_init(&p,....);
etc.

Now i'm wondering why they do it this way? if i pass a plain pointer
then it should be ok to declare a local pointer and allocate and
initialize it. after initialization it can be returned by the fuction.

Words may be getting in the way here! You do declare and allocate a
local pointer, and there is no reason you can't initialise it (you
assign to it, but you could have initialised it if you'd wanted to).

There are several things you could mean. Maybe you are asking why you
can't allocate a local struct module_ds object? That's usually because
the library writer wants to keep you from messing up some important
internal state. To day that, they will make struct module_ds
incomplete so you can't declare one.

Alternatively you might be asking why you can't allocate the storage
that p should point to yourself. The answer to that may be the same.
The full declaration of the struct may, deliberately, not be visible so
you don't know how much storage to allocate, but it might simply be
convenience. Why would you want to? It's simpler if the library
provides a function to do it for you.

One question is very clear: why is the function return type "void"? I
don't know. That's almost always a mistake in my option. A function
should only rarely want to waste the opportunity to return something
helpful. I'd want:

struct module_ds *p = module_init(...);

But another option is to return an error value:

struct module_ds *p;
if (!module_init(&p, ...)) /* act on the failure */
but they always have a void type.

I agree this is odd.
Even if they remain "void" still it should be possible to allocate,
and initialize a local pointer and assign it to a plain pointer comes
as a argument?

Sorry, but I don't really follow what you mean here.
 
B

Ben Bacarisse

China Blue Sea said:
I prefer the first with the general paradigm of
output = function(input)

However some people take 'function' literally and want a function to
be free of external side effects. They use the general paradigm of

function(input, &output)
or
function(&output, input)

Surely it is the first (the one we both prefer) that stresses the pure
functional nature of "function". I'd say that the latter two explicitly
stress side effects.

<snip>
 
E

Eric Sosman

[reformatted for line length]
I have used many OSS libraries which have a common structure like
the following:

They have init function which takes a pointer to pointer to the
module data structure like this:
module_init(struct module_ds ** p,.....);
And usage example is like following:

struct module_ds * p;
p = NULL;
module_init(&p,....);
etc.

Now i'm wondering why they do it this way? if i pass a plain pointer
then it should be ok to declare a local pointer and allocate and
initialize it. after initialization it can be returned by the fuction.

That's another style, also often seen:

struct module_ds * p;
p = module_init(...);
but they always have a void type.

I'm not sure what you mean by "they." If it's "The module_init()
functions," then I must disagree about the "always." Such functions
often return a code describing success or failure, or a pointer to a
freshly-initialized struct. They could, of course, return almost
anything their designers dream up -- but in my experience it's usually
one of these two. Initialization functions that return nothing are
(again, in my experience) comparatively rare.
Even if they remain "void" still it should be possible to allocate,
and initialize a local pointer and assign it to a plain pointer comes
as a argument?

No: A function cannot change its arguments. It can change its
parameters -- the local variables that are initialized from the call's
argument expressions -- and it can change things its arguments point
to, but it cannot change the arguments. If module_init() allocated a
new struct and set one of its parameters to point at that new struct,
the effect on the caller would be precisely nil.
Or "local" pointer is key here?

Again, I'm not sure what you're asking.

One possible use case for the pointer-to-pointer style could be
in a module that wanted to make "re-initialization" a no-op. The
initialization function might look like

void module_init(struct module_ds **p, /* other stuff */ ) {
if (*p == NULL) {
*p = malloc(sizeof **p);
if (*p != NULL) {
/* initialize the new struct */
}
}
}

That is, a new struct is allocated and initialized only if the
pointed-to pointer is NULL; nothing happens if initialization has
already occurred. This would allow

struct module_ds *p = NULL;
if (this) {
module_init(&p,...);
...
}
if (that) {
module_init(&p,...);
...
}
if (the_other) {
module_init(&p,...);
...
}

In this use, the module is never initialized more than once, and
is not initialized at all if this, that, and the_other are all false.
You could achieve the same effect with other styles, but it might
take more writing:

struct module_ds *p = NULL;
if (this) {
if (p == NULL) p = module_init(...);
...
}
if (that) {
if (p == NULL) p = module_init(...);
...
}
if (the_other) {
if (p == NULL) p = module_init(...);
...
}
 
A

ArifulHossain tuhin

[reformatted for line length]
I have used many OSS libraries which have a common structure like
the following:

They have init function which takes a pointer to pointer to the
module data structure like this:
module_init(struct module_ds ** p,.....);
And usage example is like following:

struct module_ds * p;
p = NULL;
module_init(&p,....);
etc.

Now i'm wondering why they do it this way? if i pass a plain pointer
then it should be ok to declare a local pointer and allocate and
initialize it. after initialization it can be returned by the fuction.

That's another style, also often seen:

struct module_ds * p;
p = module_init(...);
but they always have a void type.

I'm not sure what you mean by "they." If it's "The module_init()
functions," then I must disagree about the "always." Such functions
often return a code describing success or failure, or a pointer to a
freshly-initialized struct. They could, of course, return almost
anything their designers dream up -- but in my experience it's usually
one of these two. Initialization functions that return nothing are
(again, in my experience) comparatively rare.
Even if they remain "void" still it should be possible to allocate,
and initialize a local pointer and assign it to a plain pointer comes
as a argument?

No: A function cannot change its arguments. It can change its
parameters -- the local variables that are initialized from the call's
argument expressions -- and it can change things its arguments point
to, but it cannot change the arguments. If module_init() allocated a
new struct and set one of its parameters to point at that new struct,
the effect on the caller would be precisely nil.


That's where i'm missing the point.thank you.
Again, I'm not sure what you're asking.

One possible use case for the pointer-to-pointer style could be
in a module that wanted to make "re-initialization" a no-op. The
initialization function might look like

void module_init(struct module_ds **p, /* other stuff */ ) {
if (*p == NULL) {
*p = malloc(sizeof **p);
if (*p != NULL) {
/* initialize the new struct */
}
}
}

That is, a new struct is allocated and initialized only if the
pointed-to pointer is NULL; nothing happens if initialization has
already occurred. This would allow

struct module_ds *p = NULL;
if (this) {
module_init(&p,...);
...
}
if (that) {
module_init(&p,...);
...
}
if (the_other) {
module_init(&p,...);
...
}

In this use, the module is never initialized more than once, and
is not initialized at all if this, that, and the_other are all false.
You could achieve the same effect with other styles, but it might
take more writing:

struct module_ds *p = NULL;
if (this) {
if (p == NULL) p = module_init(...);
...
}
if (that) {
if (p == NULL) p = module_init(...);
...
}
if (the_other) {
if (p == NULL) p = module_init(...);
...
}

That's seems more than reasonable.
 
J

Johann Klammer

ArifulHossain said:
I have used many OSS libraries which have a common structure like the following:

They have init function which takes a pointer to pointer to the module data structure like this:
module_init(struct module_ds ** p,.....);
And usage example is like following:

struct module_ds * p;
p = NULL;
module_init(&p,....);
etc.

Are you *sure* it is not:

struct module_ds p;//!? no * here ?!
module_init(&p,....);

I think this is seen most often...
Developers may choose this option to seperate *instantiation* from
*initialisation*.

So one _may_ create the structure in various ways (using malloc, as an
auto var, or as global data...)

It allows for more freedom.

the call to module_init then just initializes content, but does not
allocate anything.

Certain OO purists may frown on that approach, as a pointer type can at
least be set to NULL to signify invalid, while an uninitialized
structure is more likely to cause all sorts trouble.
Now i'm wondering why they do it this way? if i pass a plain pointer then it should be ok to declare a local pointer and allocate and initialize it. after initialization it can be returned by the fuction.
It seems they they want to *write* your p pointer.
probably something like:

void module_init(struct module_ds **points_to_p,....);
{
*points_to_p=malloc(whatever);
}
but they always have a void type.
return type, I assume.
Yes, you are right. If it was just about allocating, it would be easier
to return it.
Even if they remain "void" still it should be possible to allocate, and initialize a local pointer and assign it to a plain pointer comes as a argument?

Or "local" pointer is key here?
parameters passed to functions get copied and live only inside that
function. They are similar to auto variables, but initialized. Change
does not propagate up the call tree.

The 'plain pointer comes as an argument' will not be seen outside.
 
N

Nobody

Are you *sure* it is not:

struct module_ds p;//!? no * here ?!
module_init(&p,....);

I think this is seen most often...

The advantage of the former is that it allows "struct module_ds" to be an
opaque type. Client code doesn't need to know the details of the
structure, not even its size. This can be useful if the library has
compile-time options which affect the size or layout of the structure, as
it doesn't require the client to be re-compiled to use a different
configuration of the library.
 
T

tom st denis

If you want to return (Type*), you have to either have the function return it
    Type *function(...);
    Type *var = function(...);

assign it to an output parameter
    void function(Type **var,...);
    Type *var; function(&var, ...);

or assign it to/through a global variable
    extern Type *functionvalue;
    voidfunction(...);
    function(...);
    Type *var = functionvalue;

I prefer the first with the general paradigm of
    output = function(input)

However some people take 'function' literally and want a function to be free of
external side effects. They use the general paradigm of
    function(input, &output)

The advantage of something like

int function(struct foo **param);

Is that you can return an error code AND initialize a pointer.

It's better when you have things like

int function(void **foo);

So that the data type is opaque to the user. This is handy in a
plugin scenario (like in bignum math where you might use different
structures for integers depending on the provider).

Tom
 
P

Philip Lantz

Ben said:
Surely it is the first (the one we both prefer) that stresses the pure
functional nature of "function". I'd say that the latter two explicitly
stress side effects.

I'm sure that when he said "some people take 'function' literally", what
he was referring to is that most functions in most C programs are not
free of external side effects, and that some people take that to mean
that they must not return a value, and any result must be returned via a
pointer parameter.
 
J

James Kuyper

I'm sure that when he said "some people take 'function' literally", what
he was referring to is that most functions in most C programs are not
free of external side effects, and that some people take that to mean
that they must not return a value, and any result must be returned via a
pointer parameter.

Returning a value through a pointer parameter requires use of assignment
to the pointed-at object, from within the called function, which is a
side-effect that is external to the called function. Returning a value
does not have side effects. The returned value may end up being assigned
somewhere, but that assignment would occur inside the calling function,
not the called one.
 
N

Nick Keighley

I'm sure that when he said "some people take 'function' literally", what
he was referring to is that most functions in most C programs are not
free of external side effects, and that some people take that to mean
that they must not return a value, and any result must be returned via a
pointer parameter.

they have their logic strangly inverted then!

In order to avoid a non-existent side effect they introduce a real
side effect!

To be honest I regard

getCurrentLargeObject (&currentLargeObject);

as morally a function. I'm only using a pointer because C is rubbish
at assigning large objects.

I also might use a ptr if I want to use the return value to signal an
error

rv = findMatchingTherblib (&therblig, matchp);
 
N

Nick Keighley

Returning a value through a pointer parameter requires use of assignment
to the pointed-at object, from within the called function, which is a
side-effect that is external to the called function. Returning a value
does not have side effects. The returned value may end up being assigned
somewhere, but that assignment would occur inside the calling function,
not the called one.
 
P

Philip Lantz

Nick said:
they have their logic strangly inverted then!

In order to avoid a non-existent side effect they introduce a real
side effect!

No, I'm sorry, both you and James didn't get what I was trying to say.
My point was, for people that feel that way, if they have a C function
that *does* have side effects (as most do, in my experience), then they
feel that the C function is not a "function" (in their terms), and so it
must not return a value. If the function doesn't have any other side
effects, I'm sure that they would not introduce one, but rather would be
happy to return a value in the normal way.

I don't happen to agree with this constraint, but it's not totally
nonsensical. (Totally pointless, I would say, but not nonsensical.)

Does that clear things up?
 
J

James Kuyper

On 03/01/2012 04:06 AM, Philip Lantz wrote:
....
I don't happen to agree with this constraint, but it's not totally
nonsensical. (Totally pointless, I would say, but not nonsensical.)

Does that clear things up?

Not really, but that's generally what happens when someone tries to
describe a point of view that they don't agree with. I don't think it's
worth discussing any further.
 
J

Joe keane

To be honest I regard

getCurrentLargeObject (&currentLargeObject);

as morally a function. I'm only using a pointer because C is rubbish
at assigning large objects.

Did you time it? Very likely the compiler will generate the exact same
code no matter which form you use.
 
W

Willem

Joe keane wrote:
) In article <e5f5655c-776a-4d9e-8af8-d3bcaf130c14@gr6g2000vbb.googlegroups.com>,
)>To be honest I regard
)>
)> getCurrentLargeObject (&currentLargeObject);
)>
)>as morally a function. I'm only using a pointer because C is rubbish
)>at assigning large objects.
)
) Did you time it? Very likely the compiler will generate the exact same
) code no matter which form you use.

Only if both functions are in the same unit, or if the compiler can inline
functions across units.


SaSW, Willem
--
Disclaimer: I am in no way responsible for any of the statements
made in the above text. For all I know I might be
drugged or something..
No I'm not paranoid. You all think I'm paranoid, don't you !
#EOT
 
S

Stephen Sprunk

To be honest I regard

getCurrentLargeObject (&currentLargeObject);

as morally a function. I'm only using a pointer because C is rubbish
at assigning large objects.

It's my experience that "assigning large objects" is rarely the correct
solution anyway; in most cases, it's much easier to work with pointers
to those objects rather than the objects themselves.

You can also return a pointer-to-const-object if the caller should be
required to make a copy (via memcpy() or the like) of the object rather
than modify the pointed-to one that is returned.
I also might use a ptr if I want to use the return value to signal an
error

rv = findMatchingTherblib (&therblig, matchp);

That's one variant of the hack around C's poor support for returning two
values, which has led to all sorts of problems over the years.

Still, if you return a pointer-to-therblib (see above), then returning
NULL is an obvious error signal. It's only complicated if you need to
return more than one type of error--which I suspect is where errno came
from.

S
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,770
Messages
2,569,586
Members
45,097
Latest member
RayE496148

Latest Threads

Top