Pointer problem when trying to abstract away an array type

  • Thread starter Asbjørn Sæbø
  • Start date

A

Asbjørn Sæbø

Good afternoon, gentlemen of the C group,

I would appreciate your comments to the following:

The situations is that I have a data type, the details of which I
intended to hide from the users of that module. They have access to
the type definition, and to a number of functions for doing operations
on variables of that type, but they should never try to operate
directly on variables of that type them selves, they should use the
provided function interface.
One of the things we need to do with wariables of this type is to pass
around pointers to them. But as it happens, the type is currently
implemented as an array (the users should not really care, and the
implementation has changed in the past), and that makes creating and
passing pointers to this type somewhat difficult. See example program
below.

So, the question is: Given that I want to hide the details of what
this type is from the users, and that I want pointers to this type to
behave as pointers to "normal" types, what is the best approach?

With kind regards
Asbjørn Sæbø

/* Example program ******************************************************************/

#include <stdint.h>

#define OTHER_OBJECT_SIZE 5

typedef uint8_t object_t; // A "normal" type
typedef uint8_t other_object_t[OTHER_OBJECT_SIZE]; // A type that happens to be an array


/* A function that works on an object of the array type */
void my_func(other_object_t other_object);
void my_func(other_object_t other_object)
{
uint8_t k;
for( k = 0; k < OTHER_OBJECT_SIZE; k++ )
{
other_object[k] = k;
}
}


int main( void )
{
object_t my_object;
object_t * p_object;

other_object_t my_other_object;
other_object_t * p_other_object;

uint8_t * p_uint8_t;

/* Operations on the "normal" object */
// p_object = my_object; // Compiler error, tries to convert uint8_t to pointer
p_object = &my_object; // OK, no warnings


/* Operations on the array object */
p_other_object = my_other_object; // Compiler warning - pointer to different objects
// Lint warning: Type mismatch (unsigned char (*)[5] = unsigned char *)

p_other_object = &my_other_object; // Compiler warning - pointer to different objects
// Lint warning: Suspicious use of &


/* Using a uint8_t pointer to access the array object */
p_uint8_t = my_other_object; // OK to compiler, and to Lint

p_uint8_t = &my_other_object; // OK to compiler
// Lint warning: Type mismatch (unsigned char (*)[5] = unsigned char *)
// Lint warning: Suspicious use of &

p_uint8_t = &my_other_object[0]; // OK to compiler and to Lint


/* Passing a pointer to the array object to a function */
my_func(my_other_object); // OK to compiler and Lint, but hides that we are passing a pointer

my_func(&my_other_object); // Lint warning: Type mismatch (unsigned char (*)[5] = unsigned char *)
// Lint warning: Suspicious use of &


return 0;
}
 
Ad

Advertisements

J

James Kuyper

Asbjørn Sæbø said:
Good afternoon, gentlemen of the C group,

I would appreciate your comments to the following:

The situations is that I have a data type, the details of which I
intended to hide from the users of that module. They have access to
the type definition, and to a number of functions for doing operations
on variables of that type, but they should never try to operate
directly on variables of that type them selves, they should use the
provided function interface.
One of the things we need to do with wariables of this type is to pass
around pointers to them. But as it happens, the type is currently
implemented as an array (the users should not really care, and the
implementation has changed in the past), and that makes creating and
passing pointers to this type somewhat difficult. See example program
below.

So, the question is: Given that I want to hide the details of what
this type is from the users, and that I want pointers to this type to
behave as pointers to "normal" types, what is the best approach?

Put the array in a struct. In header files for use by the users of the
code, just declare:

struct array_wrapper;

Without giving any details.
 
A

Asbjørn Sæbø

James Kuyper said:
Asbjørn Sæbø wrote:
[...] But as it happens, the type is currently
implemented as an array (the users should not really care, and the
implementation has changed in the past), and that makes creating and
passing pointers to this type somewhat difficult. See example program
below. So, the question is: Given that I want to hide the details of
what
this type is from the users, and that I want pointers to this type to
behave as pointers to "normal" types, what is the best approach?
Put the array in a struct. In header files for use by the users of the
code, just declare:

struct array_wrapper;

Without giving any details.

That is quite similar to what I once did have. The problem, which I
forgot to mention, is that this is in a hard realtime system
implemented on an 8051, and the struct seemed to give quite some
overhead when accessing the elements of the array that was hidden in
the struct. I might have to go that way again, but I would rather see
if there is another solution first.

Asbjørn
 
E

Eric Sosman

James said:
Put the array in a struct. In header files for use by the users of the
code, just declare:

struct array_wrapper;

Without giving any details.

Addendum: This isn't strictly necessary -- you could
solve your stated problem with `void*' pointers instead --
but provides an additional benefit of some compile-time
type-checking. For example, if one of your functions takes
a `struct array_wrapper *' parameter and somebody tries to
pass it `struct tm *' instead, the compiler will complain and
alert him to the mismatch. If you used `void*' the compiler
would complain if the caller tried to pass a `double' or
something, but would accept any kind of data pointer, correct
or incorrect.

Besides, it will very often turn out that the "bare"
array isn't quite enough for your hidden data type. You're
likely to find a need to store a length, or a few status
flags, or some other bits and pieces of ancillary information,
and if you've already set yourself up to use a struct this
will be simple. It's possible you'll never need the extra
flexibility, but keeping the option open is a good thing.
 
B

Ben Bacarisse

Asbjørn Sæbø said:
Good afternoon, gentlemen of the C group,

I would appreciate your comments to the following:

The situations is that I have a data type, the details of which I
intended to hide from the users of that module. They have access to
the type definition, and to a number of functions for doing operations
on variables of that type, but they should never try to operate
directly on variables of that type them selves, they should use the
provided function interface.
One of the things we need to do with wariables of this type is to pass
around pointers to them. But as it happens, the type is currently
implemented as an array (the users should not really care, and the
implementation has changed in the past), and that makes creating and
passing pointers to this type somewhat difficult. See example program
below.

So, the question is: Given that I want to hide the details of what
this type is from the users, and that I want pointers to this type to
behave as pointers to "normal" types, what is the best approach?

The best way to hide a type is to use only pointers. You put
everything into a struct and publicly define only:

typedef struct my_object_structure *my_opaque_type;

The "user code" can't see what's in a struct my_object_structure so it
can't do anything with it. You can change it at will. There are some
consequences. You are forced to have a level if indirection you might
not want and everything has to be allocated in some way -- users can't
just declare objects of your type.

If you don't want to go that route, you are permitted to have pointer
to arrays. In the example below, it is not clear what you want to
allow and what you want to forbid, but a pointer to and array is valid
C type.
/* Example program ******************************************************************/

#include <stdint.h>

#define OTHER_OBJECT_SIZE 5

typedef uint8_t object_t; // A "normal" type
typedef uint8_t other_object_t[OTHER_OBJECT_SIZE]; // A type that happens to be an array


/* A function that works on an object of the array type */
void my_func(other_object_t other_object);
void my_func(other_object_t other_object)
{
uint8_t k;
for( k = 0; k < OTHER_OBJECT_SIZE; k++ )
{
other_object[k] = k;
}
}


int main( void )
{
object_t my_object;
object_t * p_object;

other_object_t my_other_object;
other_object_t * p_other_object;

uint8_t * p_uint8_t;

/* Operations on the "normal" object */
// p_object = my_object; // Compiler error, tries to convert uint8_t to pointer
p_object = &my_object; // OK, no warnings


/* Operations on the array object */
p_other_object = my_other_object; // Compiler warning - pointer to different objects
// Lint warning: Type mismatch (unsigned char (*)[5] = unsigned char *)

p_other_object = &my_other_object; // Compiler warning - pointer to different objects
// Lint warning: Suspicious use of &


/* Using a uint8_t pointer to access the array object */
p_uint8_t = my_other_object; // OK to compiler, and to Lint

p_uint8_t = &my_other_object; // OK to compiler
// Lint warning: Type mismatch (unsigned char (*)[5] = unsigned char *)
// Lint warning: Suspicious use of &

p_uint8_t = &my_other_object[0]; // OK to compiler and to Lint


/* Passing a pointer to the array object to a function */
my_func(my_other_object); // OK to compiler and Lint, but hides that we are passing a pointer

my_func(&my_other_object); // Lint warning: Type mismatch (unsigned char (*)[5] = unsigned char *)
// Lint warning: Suspicious use of &


return 0;
}

I was going to annotate the above to use pointers the array type but I
decided that I really did not know what it was you wanted to achieve.
Rather than say what the compiler tells you, tell us what operations
you'd like to permit and what you'd like to forbid.

I suspect the problem is that you want to mix the array and the
non-array type in ways that reveals that they are related, but that is
just a guess. What should the API user be able to write, and what
should be detected by the compiler?
 
A

Asbjørn Sæbø

Ben Bacarisse said:
Asbjørn Sæbø <[email protected]> writes:
If you don't want to go that route, you are permitted to have pointer
to arrays.
Indeed

In the example below, it is not clear what you want to
allow and what you want to forbid, but a pointer to and array is valid
C type.

The main point below was that for a "normal" object of some kind you
would do &object to get a pointer to it, while that is not appropriate
for an array (since an array will decay into a pointer by itself). So
the the handling of pointers to these array objects is different from
the handling of pointers to other objects, and also betrays the fact
that the object in question is an array.

/* Operations on the "normal" object */
// p_object = my_object; // Compiler error, tries to convert uint8_t to pointer
p_object = &my_object; // OK, no warnings


/* Operations on the array object */
p_other_object = my_other_object; // Compiler warning - pointer to different objects
// Lint warning: Type mismatch (unsigned char (*)[5] = unsigned char *)

p_other_object = &my_other_object; // Compiler warning - pointer to different objects
// Lint warning: Suspicious use of &

I was going to annotate the above to use pointers the array type but I
decided that I really did not know what it was you wanted to achieve.

Baically, I would like to get a pointer to the object by saying
"&object", even if the object happens to be an array.


Asbjørn Sæbø
 
Ad

Advertisements

J

James Kuyper

Asbjørn Sæbø said:
James Kuyper said:
Asbjørn Sæbø wrote:
[...] But as it happens, the type is currently
implemented as an array (the users should not really care, and the
implementation has changed in the past), and that makes creating and
passing pointers to this type somewhat difficult. See example program
below. So, the question is: Given that I want to hide the details of
what
this type is from the users, and that I want pointers to this type to
behave as pointers to "normal" types, what is the best approach?
Put the array in a struct. In header files for use by the users of the
code, just declare:

struct array_wrapper;

Without giving any details.

That is quite similar to what I once did have. The problem, which I
forgot to mention, is that this is in a hard realtime system
implemented on an 8051, and the struct seemed to give quite some
overhead when accessing the elements of the array that was hidden in
the struct.


That doesn't seem plausible to me. Did you pass passing around copies of
the struct itself, rather than a pointer to the struct? If so, that
mistake might explain why you wre having troubles. Unless there's
something very unusual going on, a decent compiler should generate
essentially the same output code for access to an array via a pointer to
the array, as for access to an identical array which is the first member
of a structure via a pointer to the structure.

Even if the compiler does do something stupid with such code, it
shouldn't be difficult to work around. Internal to your code,
unavailable to the users, you would have:

struct array_wrapper{ element_type array[ARRAY_SIZE];}

In your functions that use the opaque type, such as

void funct(struct array_wrapper* parrwrap)
{

Then instead of writing:

parrwrap->array[particular_element]

You would write the following line near the top of your function:

element_type *parr = parrwrap->array;

and then use

parr[particular_element]
 
B

Ben Bacarisse

Asbjørn Sæbø said:
The main point below was that for a "normal" object of some kind you
would do &object to get a pointer to it, while that is not appropriate
for an array (since an array will decay into a pointer by itself). So
the the handling of pointers to these array objects is different from
the handling of pointers to other objects, and also betrays the fact
that the object in question is an array.

My point what that you should not use that decay. If you need to pass
a pointer to an object, use &object even when it is an array type.
There is nothing wrong with doing this.
/* Operations on the "normal" object */
// p_object = my_object; // Compiler error, tries to convert uint8_t to pointer
p_object = &my_object; // OK, no warnings


/* Operations on the array object */
p_other_object = my_other_object; // Compiler warning - pointer to different objects
// Lint warning: Type mismatch (unsigned char (*)[5] = unsigned char *)

p_other_object = &my_other_object; // Compiler warning - pointer to different objects
// Lint warning: Suspicious use of &

I was going to annotate the above to use pointers the array type but I
decided that I really did not know what it was you wanted to achieve.

Baically, I would like to get a pointer to the object by saying
"&object", even if the object happens to be an array.

Originally I wrote: "Then what is wrong with writing &object? You get
a pointer to the array." but then I saw the warning you are getting
from your compiler.

I suspect that the problem is that your compiler is misbehaving. In
particular, the line:

p_other_object = &my_other_object;

is fine and there should be no complaint about it. What compiler is
it and are you invoking it in conforming mode? By this I mean are you
asking for strict adherence to the C standard (either C90 or C99)?
 
A

Asbjørn Sæbø

I suspect that the problem is that your compiler is misbehaving. In
particular, the line:

p_other_object = &my_other_object;

is fine and there should be no complaint about it. What compiler is
it and are you invoking it in conforming mode? By this I mean are you
asking for strict adherence to the C standard (either C90 or C99)?

It is the Keil C51 compiler. According to the documentation is is
based on C90, and the list of implementation-defined behaviour and
differences from the ANSI C Standard does not seem to mention this.
I could try asking Keil about this.


On a related note: Is typedef-ing arrays consdered a bad habit?
Like
typedef uint8_t mytype[SOME_SIZE]


Asbjørn Sæbø
 
B

Ben Bacarisse

Asbjørn Sæbø said:
It is the Keil C51 compiler. According to the documentation is is
based on C90, and the list of implementation-defined behaviour and
differences from the ANSI C Standard does not seem to mention this.
I could try asking Keil about this.

I think so. Something odd is going on. In ANSI C (usually a
shorthand for C90, the equivalent ISO standard) // comments are a
syntax error, there is no <stdint.h> (though it can be provided as an
extension) and mixing statements and declarations is not permitted.
It is not clear what language the compiler is accepting.
On a related note: Is typedef-ing arrays consdered a bad habit?
Like
typedef uint8_t mytype[SOME_SIZE]

If it meets your needs, go for it. Obviously the conversion to
pointer might trip up some unwary maintenance programmer, so I'd be
sure to document it well.
 
C

CBFalconer

Asbjørn Sæbø said:
The situations is that I have a data type, the details of which I
intended to hide from the users of that module. They have access
to the type definition, and to a number of functions for doing
operations on variables of that type, but they should never try
to operate directly on variables of that type them selves, they
should use the provided function interface. .... snip ....

So, the question is: Given that I want to hide the details of what
this type is from the users, and that I want pointers to this type
to behave as pointers to "normal" types, what is the best approach?

Hide everything. The only thing the user knows about is the
operating functions and a pointer to the data. Something typical:

/* ------- main file for thing.c ------- */
#include "thing.h" /* includes an incomplete definition of thing*
*/

/* anything not marked as static, and at the global level, is
available to users */

typedef struct athing {
/* all the details of what it contains */
/* NOT visible outside this file */
/* define pointers to thing as "struct athing *name;" */
} thing;

thing *opone(thing *val1, thing *val2) { /* code */ }
thing *optwo(thing *val1, thing *val2) { /* code */ }
thing *makething(int param1, int param2) {
thing *newptr;

if (newptr = malloc(sizeof *newptr) {
/* code */
}
return newptr;
}
/* ---------- end of thing.c ----------- */


/* ---------- header thing.h ------------*/

/* note this is an incomplete struct definition, */
/* and that all ptrs to struct are the same size */
typedef struct athing thing;

thing *opone(thing *val1, thing *val2);
thing *optwo(thing *val1, thing *val2);
thing *makething(int param1, int param2);
/* ------- end of header thing.h --------*/

With this pair, the user knows nothing about the code in thing.c.
He knows (if he #includes "thing.h") how to make a thing, how to
execute an opone or an optwo on two things, and get the result. He
knows nothing about what lies within the result. You can make
added functions to return such info.
 
Ad

Advertisements

J

JosephKK

James Kuyper said:
Asbjørn Sæbø wrote:
[...] But as it happens, the type is currently
implemented as an array (the users should not really care, and the
implementation has changed in the past), and that makes creating and
passing pointers to this type somewhat difficult. See example program
below. So, the question is: Given that I want to hide the details of
what
this type is from the users, and that I want pointers to this type to
behave as pointers to "normal" types, what is the best approach?
Put the array in a struct. In header files for use by the users of the
code, just declare:

struct array_wrapper;

Without giving any details.

That is quite similar to what I once did have. The problem, which I
forgot to mention, is that this is in a hard realtime system
implemented on an 8051, and the struct seemed to give quite some
overhead when accessing the elements of the array that was hidden in
the struct. I might have to go that way again, but I would rather see
if there is another solution first.

Asbjørn

A hard real time system on an 8051 that is not a one person show?
Requiring multiple programmers and data hiding like C++? There is
something very strange here.
 

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

Top