Converting between object pointers

L

Lauri Alanko

The standard (6.3.2.3#7) makes conversion between object types
well-defined when a converted pointer is "correctly aligned" for the
referenced type. But in what situations can a portable program trust
that a pointer of type A* is correctly aligned for conversion to B*?
Certainly when B is a character type (or void), or when the A* pointer
has been converted from the return value of a malloc call. But are
there any other cases?

How about the case when B is an incomplete type? Is the following
guaranteed to work?

typedef struct A { int x; } A;
typedef struct B B;
A a = { 42 };
B* bp = (B*) &a;
A* ap = (A*) bp;
ap->x = 54;

I can't see how this could go wrong, but could it be that B (which is
never ever completed) could somehow have stricter alignment
requirements than A, making the conversion undefined?

Thanks,


Lauri
 
J

James Kuyper

The standard (6.3.2.3#7) makes conversion between object types
well-defined when a converted pointer is "correctly aligned" for the
referenced type. But in what situations can a portable program trust
that a pointer of type A* is correctly aligned for conversion to B*?
Certainly when B is a character type (or void), or when the A* pointer
has been converted from the return value of a malloc call. But are
there any other cases?

An array type must have an alignment requirement at least as strict as
that of the element type of the array.
A struct must have an alignment requirement that is a common multiple of
the alignment requirements of all of it's members; otherwise arrays of
structs wouldn't work properly. Therefore, a pointer to any array is
correctly aligned for any of the types it has as a member.
A union must be correctly aligned for any of it's member types.
All struct pointers have the same alignment requirement.
How about the case when B is an incomplete type? Is the following
guaranteed to work?

typedef struct A { int x; } A;
typedef struct B B;
A a = { 42 };
B* bp = (B*) &a;
A* ap = (A*) bp;
ap->x = 54;

No, and no.
I can't see how this could go wrong, but could it be that B (which is
never ever completed) could somehow have stricter alignment
requirements than A, making the conversion undefined?

Yes. One way in which it could go wrong is that the conversion to B*
might round the address to the nearest location that is correctly
aligned for a struct B object, which might not be the location where 'a'
resides.
 
S

Shao Miller

The standard (6.3.2.3#7) makes conversion between object types
well-defined when a converted pointer is "correctly aligned" for the
referenced type. But in what situations can a portable program trust
that a pointer of type A* is correctly aligned for conversion to B*?

If 'A' is an aggregate or union type which has a 'B' subobject at its
beginning, then a valid 'A *' pointer value can be converted with
confidence to a 'B *'.

As an example:

union B {
int w;
double d;
};

struct middle {
union B b;
char c[10];
};

union A {
int w;
struct middle m;
};

'A' is a union type. Its member 'm' is at its beginning (all members of
a union are). 'm' is a struct type. Its member 'b' is at its
beginning. Thus the 'A' type has a 'B' member at its beginning and a
valid 'A *' value can be converted to a 'B *' value.

If the size and alignment are unknown, I don't think you have any
guarantee about the conversion's behaviour.
Certainly when B is a character type (or void), or when the A* pointer
has been converted from the return value of a malloc call.

Right. "...suitably aligned so that it may be assigned to a pointer to
any type of object..."[7.20.3p1]
But are
there any other cases?

Hmmm... Do you know the size and/or alignment for the type?
How about the case when B is an incomplete type? Is the following
guaranteed to work?

typedef struct A { int x; } A;
typedef struct B B;
A a = { 42 };
B* bp = (B*)&a;
A* ap = (A*) bp;
ap->x = 54;

I don't think so, unless 'B' is completed and you find that its
alignment requirement is <= 'A'.

In C1X, we have the 'alignof' operator and some other alignment friends.

Mr. C. M. Thomasson has offered an 'ALIGNOF' macro that goes something like:

#define ALIGNOF(type) (offsetof(struct { char c; type m; }, m))

Which seems very nice to me, except for a couple of minor weaknesses.

Both the C1X operator as well as this macro require that the type is a
completed object type.
I can't see how this could go wrong, but could it be that B (which is
never ever completed) could somehow have stricter alignment
requirements than A, making the conversion undefined?

I believe it could be the case, yes. It seems odd to think that such a
determination can ever be made if the type is never completed.

Do you have a specific goal that you are trying to accomplish, or are
you just curious? Perhaps there are other Standard guarantees which
might apply to some situation you might be thinking about.
 
L

Lauri Alanko

In C1X, we have the 'alignof' operator and some other alignment friends.

Mr. C. M. Thomasson has offered an 'ALIGNOF' macro that goes something like:

#define ALIGNOF(type) (offsetof(struct { char c; type m; }, m))

Which seems very nice to me, except for a couple of minor weaknesses.

Both the C1X operator as well as this macro require that the type is a
completed object type.

I'm aware of these, but these are only useful for runtime checks, and
here I was interested in static properties of types.

(Also, the usefulness of a standard alignof seems limited unless we
also have a standard way of manipulating addresses to ensure their
alignment, and to my understanding the new standard doesn't provide
this, only aligned_alloc. The common approach of manipulating the
lower bits of a pointer-turned-int is still implementation-defined.)
Do you have a specific goal that you are trying to accomplish, or are
you just curious? Perhaps there are other Standard guarantees which
might apply to some situation you might be thinking about.

I was considering one approach to the age-old "opaque typedef"
problem. Say I have a type for dictionaries:

typedef struct Dict Dict;
Dict* dict_new(....);
void* dict_get(Dict* dict, void* key);

Now I want to have a separate type IntDict for dicts whose keys are
ints. I can use the implementation for Dict, I just want a distinct
type to ensure that other Dicts can't be passed to IntDict functions.
One approach is this:

typedef struct IntDict IntDict;

inline IntDict* intdict_new(....) {
return (IntDict*) dict_new(....);
}

inline void* intdict_get(IntDict* idict, int key) {
return dict_get((Dict*) idict, &key);
}

This seemed appealing to me, because we get a distinct type, and
IntDict* is directly a pointer type which is convenient for its
void*-compatibility.

But if the above doesn't work, I'm forced to resort to struct-wrapping
the actual pointer:

typedef struct IntDictP {
Dict* dict_;
} IntDictP;

inline IntDictP intdict_new(....) {
return (IntDictP) { dict_new(....) };
}

inline void* intdict_get(IntDictP idict, int key) {
return dict_get(idict.dict_, &key);
}

I think this is uglier, since now the user of the intdict API has to
use a special handle type instead of an ordinary pointer. But perhaps
this is then the only portable way to do this.


Lauri
 
C

Chris M. Thomasson

Lauri Alanko said:
I'm aware of these, but these are only useful for runtime checks, and
here I was interested in static properties of types.

FWIW, the `ALIGNOF()' function macro allows one to determine a compatible
alignment for a given type at compilation time as well.

[...]
 
J

James

Lauri Alanko said:
I'm aware of these, but these are only useful for runtime checks, and
here I was interested in static properties of types.

(Also, the usefulness of a standard alignof seems limited unless we
also have a standard way of manipulating addresses to ensure their
alignment, and to my understanding the new standard doesn't provide
this, only aligned_alloc. The common approach of manipulating the
lower bits of a pointer-turned-int is still implementation-defined.)

How safe is something like this:

void* align_ptr(void* ptr, size_t align)
{
if (align > 1) {
/* assume align is power of two */
size_t base = ((unsigned char*)ptr) - 0UL;
size_t align_mask = align - 1;
size_t align_addr = (base + align_mask) & ~align_mask;
size_t align_inc = align_addr - base;
unsigned char* align_base = ((unsigned char*)ptr) + align_inc;
assert(! (align_addr % align));
return align_base;
}

return ptr;
}

?


Is there a much "safer" way to do this?

Thanks!

[...]
 
T

Tim Rentsch

Lauri Alanko said:
The standard (6.3.2.3#7) makes conversion between object types
well-defined when a converted pointer is "correctly aligned" for the
referenced type. But in what situations can a portable program trust
that a pointer of type A* is correctly aligned for conversion to B*?
Certainly when B is a character type (or void), or when the A* pointer
has been converted from the return value of a malloc call. But are
there any other cases?

How about the case when B is an incomplete type? Is the following
guaranteed to work?

typedef struct A { int x; } A;
typedef struct B B;
A a = { 42 };
B* bp = (B*) &a;
A* ap = (A*) bp;
ap->x = 54;

I can't see how this could go wrong, but could it be that B (which is
never ever completed) could somehow have stricter alignment
requirements than A, making the conversion undefined?

Practically speaking it is very unlikely to fail.

However, the Standard does allow it to fail, and it's easy
to see how to construct an implementation where it would
fail. Suppose we have an implementation where structs whose
structure tag starts with 'A' always have an alignment that
is a multiple of 16, and structs whose structure tag starts
with 'B' always have an alignment that is a multiple of 32.
(It's easy to do this just by adding enough padding.)
Furthermore suppose our implementation is "helpful" in that
after a pointer conversion it checks the converted pointer
to make sure it is suitably aligned, and aborts the program
if it isn't. All these things are allowed by the Standard,
and easily could cause the above program to misbehave, if
the 'a' structure is 16-aligned but not 32-aligned.

For trying to decide if something can fail, the "malicious
implementation" approach usually is pretty good at showing
that something can fail, if indeed the Standard does allow it
to fail.
 
S

Shao Miller

I'm aware of these, but these are only useful for runtime checks, and
here I was interested in static properties of types.

I'm not sure I grok "only useful for runtime checks." Both result in
integer constant expressions and so are suitable for such things as
enumeration values or array sizes; possible translation-time determinations.
(Also, the usefulness of a standard alignof seems limited unless we
also have a standard way of manipulating addresses to ensure their
alignment, and to my understanding the new standard doesn't provide
this, only aligned_alloc. The common approach of manipulating the
lower bits of a pointer-turned-int is still implementation-defined.)

C1X has the '_Alignas' keyword as an "alignment specifier." I don't
know of a C89/C99 counterpart.

With '*alloc'-allocated storage, you can perform your own computations
to ensure any objects stored therein adhere to whatever alignment
requirements you want. My 'pfxalloc' example in another thread provides
an example (though it could certainly use improvement).
I was considering one approach to the age-old "opaque typedef"
problem.

By co-incidence, I was pondering this one last night. :)
Say I have a type for dictionaries:

typedef struct Dict Dict;
Dict* dict_new(....);
void* dict_get(Dict* dict, void* key);

Now I want to have a separate type IntDict for dicts whose keys are
ints. I can use the implementation for Dict, I just want a distinct
type to ensure that other Dicts can't be passed to IntDict functions.

If I understand your requirements correctly, you wish to have another
"pointerish" type that can point at a 'Dict' but is nonetheless distinct
from 'Dict *'; not compatible.
One approach is this:

typedef struct IntDict IntDict;

inline IntDict* intdict_new(....) {
return (IntDict*) dict_new(....);
}

inline void* intdict_get(IntDict* idict, int key) {
return dict_get((Dict*) idict,&key);
}

This seemed appealing to me, because we get a distinct type, and
IntDict* is directly a pointer type which is convenient for its
void*-compatibility.

Hmmm... If someone has a 'Dict *' value converted to a 'void *' value,
what'll stop them from passing that to 'intdict_get', regardless of the
distinct 'IntDict *' type?
But if the above doesn't work, I'm forced to resort to struct-wrapping
the actual pointer:

Not necessarily.
typedef struct IntDictP {
Dict* dict_;
} IntDictP;

inline IntDictP intdict_new(....) {
return (IntDictP) { dict_new(....) };
}

inline void* intdict_get(IntDictP idict, int key) {
return dict_get(idict.dict_,&key);
}

I think this is uglier, since now the user of the intdict API has to
use a special handle type instead of an ordinary pointer. But perhaps
this is then the only portable way to do this.

Are they going to be passing a 'void *' or an 'xxx' for the most part?

Are you trying to prevent API users from ever including any of the
definition of 'Dict'? If not, what about using array types with
something like:

/**** dictp.h */
#ifdef DICTP_H_OK_
struct s_dict_ {
double d;
};
typedef struct s_dict_ a_int_dict_[1][1][1];
#undef DICTP_H_OK_
#endif /* DICTP_H_OK_ */



/**** dict.h */
#ifndef DICT_H_

/***
* Private details
* API users shouldn't include this header directly
*/
#define DICTP_H_OK_
#include "dictp.h"

/*** Object types */
typedef struct s_dict_ Dict;
typedef a_int_dict_ IntDict;

/*** Function declarations */
extern Dict * NewDict(void);
extern void * GetDict(Dict *, void *);
extern IntDict * NewIntDict(void);
extern void * GetIntDict(IntDict *, int);

#endif /* DICT_H_ */



/**** dict.c */
#include <stdlib.h>
#include "dict.h"

Dict * NewDict(void) {
return malloc(sizeof (Dict));
}

void * GetDict(Dict * dict, void * key) {
/* Whatever */
return 0;
}

IntDict * NewIntDict(void) {
return (IntDict *) NewDict();
}

void * GetIntDict(IntDict * idict, int key) {
/* Whatever */
return 0;
}



/**** Test program */
#include <stdlib.h>
#include "dict.h"

int main(void) {
Dict * foo = NewDict();
IntDict * bar = NewIntDict();
free(foo);
free(bar);
return 0;
}
 
C

Chris M. Thomasson

Shao Miller said:
On 6/29/2011 12:23, Lauri Alanko wrote: [...]

C1X has the '_Alignas' keyword as an "alignment specifier." I don't know
of a C89/C99 counterpart.

With '*alloc'-allocated storage, you can perform your own computations to
ensure any objects stored therein adhere to whatever alignment
requirements you want. My 'pfxalloc' example in another thread provides
an example (though it could certainly use improvement).

This one right?

http://groups.google.com/group/comp.lang.c/msg/9c40078e0d409a4e


I must be missing something important; when I do this:
________________________________________
unsigned char* foo = pfxalloc(16, 8192, 128);
________________________________________


The pointer I get back to the 128-byte object is not aligned on a 8192
boundary.


[...]
 
S

Shao Miller

How safe is something like this:

void* align_ptr(void* ptr, size_t align)
{
if (align> 1) {
/* assume align is power of two */

No need to assume. You can check:

/* Sanity-check alignment value */
assert(!(align & (align - 1)));
size_t base = ((unsigned char*)ptr) - 0UL;

What is 'base' supposed to hold at this point? It looks you are
assigning an 'unsigned char *' value to a 'size_t' object. Does that
compile?
[...more code...]

Is there a much "safer" way to do this?

To do what? Produce a particularly-aligned address? As far as I know,
only '*alloc'-allocated storage is guaranteed to begin at an address
that is suitably aligned for any object. So then the only alignment
management you can do is relative to such an an address, and your
pointer arithmetic must not go out-of-bounds for the allocated storage.
 
S

Shao Miller

This one right?

http://groups.google.com/group/comp.lang.c/msg/9c40078e0d409a4e


I must be missing something important; when I do this:
________________________________________
unsigned char* foo = pfxalloc(16, 8192, 128);
________________________________________


The pointer I get back to the 128-byte object is not aligned on a 8192
boundary.

It's relative to the start of the allocated storage. Is the start of
the allocated storage ('pfxget') at an 8,192-byte boundary?
 
C

Chris M. Thomasson

Shao Miller said:
It's relative to the start of the allocated storage.

I would expect the function to return an 128-byte object aligned on a
8192-byte boundary. From that aligned pointer, I can call `pfxget()' and
grab a pointer to the 16-byte header.

Is the start of the allocated storage ('pfxget') at an 8,192-byte
boundary?

No.
 
J

James

Shao Miller said:
No need to assume. You can check:

/* Sanity-check alignment value */
assert(!(align & (align - 1)));
Okay.



What is 'base' supposed to hold at this point?

'base' should hold the value of 'ptr'. So if `ptr' is equal to 0x00344ce8,
then base should be equal to 3427560.

It looks you are assigning an 'unsigned char *' value to a 'size_t'
object. Does that compile?

Yes, it compiles.

[...more code...]

Is there a much "safer" way to do this?

To do what? Produce a particularly-aligned address?
Yes.


As far as I know, only '*alloc'-allocated storage is guaranteed to begin
at an address that is suitably aligned for any object.

Not for any object. For instance, I believe that you need to ensure that
data passed to some SSE instructions needs to be aligned on 128-bit
boundary. Stock malloc does _not_ do this on my platform. (windows). I have
to resort to using '_aligned_malloc' from MSVC to do such things.
 
S

Shao Miller

I would expect the function to return an 128-byte object aligned on a
8192-byte boundary.

Relative to _what_? An "address 0"? I don't know that there's such a
thing in C. Is there? :)
From that aligned pointer, I can call `pfxget()' and
grab a pointer to the 16-byte header.

And the resulting address is suitably aligned for any C object supported
by the implementation.

So then the 8,192-byte alignment requirement is beyond what your
implementation deems reasonable for "suitably aligned for any type."
That's unfortunate.

However, I just tested your parameters and found that the 'pfxalloc'
address was indeed aligned to 8,192 bytes relative to the 'pfxget' address.
 
S

Shao Miller

'base' should hold the value of 'ptr'. So if `ptr' is equal to 0x00344ce8,
then base should be equal to 3427560.



Yes, it compiles.

No warning? :) (Maybe check for .wrn and .log files in your build
directory.) Although they're both scalar values, a pointer value and an
unsigned integer value strike me as being fairly different.
[...more code...]

Is there a much "safer" way to do this?

To do what? Produce a particularly-aligned address?

Yes.

Oh, ok. "Safer" meaning "portable" or meaning "not getting a false
result"? (Or meaning something else?)
Not for any object.

Right. I meant any C object. That's guaranteed by the Standard.
Anything with a stricter alignment requirement than is met by 'malloc'
is non-Standard.
For instance, I believe that you need to ensure that
data passed to some SSE instructions needs to be aligned on 128-bit
boundary. Stock malloc does _not_ do this on my platform. (windows).

Ah yes. Outside of Standard C.
I have
to resort to using '_aligned_malloc' from MSVC to do such things.

They probably have that because it's outside of Standard C. It's
probably the Right Way for your requirement(s).

If you are interested producing your own non-portable function to yield
aligned pointers, that should be easy if you know the pointer
representation for your particular implementation.
 
J

James

Shao Miller said:
No warning? :) (Maybe check for .wrn and .log files in your build
directory.) Although they're both scalar values, a pointer value and an
unsigned integer value strike me as being fairly different.

Yikes! I don't know why I did not simply do this instead:

size_t base = (size_t)ptr;

DOH!

:^(

[...more code...]

Is there a much "safer" way to do this?

To do what? Produce a particularly-aligned address?

Yes.

Oh, ok. "Safer" meaning "portable" or meaning "not getting a false
result"? (Or meaning something else?)

Safer meaning "likely" to work on most existing modern platforms...

Right. I meant any C object. That's guaranteed by the Standard. Anything
with a stricter alignment requirement than is met by 'malloc' is
non-Standard.


Ah yes. Outside of Standard C.


They probably have that because it's outside of Standard C. It's probably
the Right Way for your requirement(s).
touché!


If you are interested producing your own non-portable function to yield
aligned pointers, that should be easy if you know the pointer
representation for your particular implementation.

This is exactly what I want to do. I am wondering if I am going in the right
direction here. Can you think of any improvements on my little non-portable
align_ptr() function?


Thanks!

:^)
 
C

Chris M. Thomasson

Shao Miller said:
Relative to _what_? An "address 0"? I don't know that there's such a thing
in C. Is there? :)

I would expect the function to give me a pointer with an address value that
is aligned on a 8192-byte boundary such that it passes the following
constraint:

assert(! (((size_t)returned_ptr) % 8192UL));

And the resulting address is suitably aligned for any C object supported
by the implementation.


So then the 8,192-byte alignment requirement is beyond what your
implementation deems reasonable for "suitably aligned for any type."
That's unfortunate.

However, I just tested your parameters and found that the 'pfxalloc'
address was indeed aligned to 8,192 bytes relative to the 'pfxget'
address.

Okay. I am getting into non-portable land here, but the address retuned from
`pfxalloc()' using my parameters does not pass the following assertion:

assert(! (((size_t)returned_ptr) % 8192UL));

I was expecting it to do so. Non-portability aside for a moment, this can be
important... For instance, read section 2.2 of the following paper:

http://software.intel.com/file/27087
(this will result in the download of a PDF)

Sometimes, we need some way to align dynamically allocated memory on
specific boundaries...


;^)
 
S

Shao Miller

Yikes! I don't know why I did not simply do this instead:

size_t base = (size_t)ptr;

I've seen that one used in a few code-bases. If you are assuming a
linear address, it might be worth-while checking with something like:

assert(sizeof sizeof 1 >= sizeof (void *));

That is, the size of a 'size_t' is large enough to hold the
representation of a 'void *'.
Safer meaning "likely" to work on most existing modern platforms...

But there're segmented architectures where alignment concerns might be
relative to the segment and a "linear address 0" might not have meaning.
But why would you care about "SSE data on most existing modern platforms"?

I'd suggest that a good choice for each platform would be an
implementation that provides alignment extensions. Perhaps you could
abstract several of these implementation-specific extensions with
preprocessing, then provide a common interface.
This is exactly what I want to do. I am wondering if I am going in the right
direction here. Can you think of any improvements on my little non-portable
align_ptr() function?

I think you need some assumptions, such as:

- Linear addressing scheme where "address 0" has meaning and is aligned
for all concerns.

- Pointer representation resembles an unsigned integer with no padding bits.

Then you could attempt to gain some information about each
implementation with something like:

#include <assert.h>

int main(void) {
typedef unsigned char * byte_ptr;
static char foo[2];
void
* vp1 = foo,
* vp2 = foo + 1;
byte_ptr
vp1i = (byte_ptr) &vp1,
vp1e = vp1i + sizeof vp1,
vp2i = (byte_ptr) &vp2;

/* Examine bytes */
while (vp1i < vp1e) {
if (*vp1i == *vp2i - 1)
break;
++vp1i;
++vp2i;
}

if (vp1i == (byte_ptr) &vp1)
assert(!"It seems that the first byte differs!");
else if (vp1i == vp1e - 1)
assert(!"It seems that the last byte differs!");
else
assert(!"Dunno how to handle this!");

return 0;
}

Other interesting things to inspect might be the bytes of pointers
returned by 'malloc(1)' (or other sizes). Having probed for some of
this information, you could have more confidence regarding manipulating
the bytes of a pointer's representation directly. (It's still not
guaranteed to work, of course, but that's just too bad.)

This sort of trades assumptions for different assumptions... In your
code, you're assuming that casting a pointer to a 'size_t' translates it
directly into an unsigned linear address. You could probe that theory,
as well!

#include <stddef.h>
#include <assert.h>

int main(void) {
static char foo[2];
void
* vp1 = foo,
* vp2 = foo + 1;
size_t
sz1 = (size_t) vp1,
sz2 = (size_t) vp2;

if (sz1 == sz2 - 1)
assert(!"Casting to 'size_t' might be ok!");
else
assert(!"Dunno how to handle this!");

return 0;
}
 
S

Shao Miller

I would expect the function to give me a pointer with an address value that
is aligned on a 8192-byte boundary such that it passes the following
constraint:

assert(! (((size_t)returned_ptr) % 8192UL));

Well, that cast operation is non-portable and the 'pfx*' code is
supposed to be portable. That explains that. Heheheh.
Okay. I am getting into non-portable land here, but the address retuned from
`pfxalloc()' using my parameters does not pass the following assertion:

assert(! (((size_t)returned_ptr) % 8192UL));

As far as I know, since C doesn't include a linear address model, there
isn't a portable way to achieve this in at least C89 and C99. (Some C99
implementations might provide 'intptr_t' and 'uintptr_t'.)
I was expecting it to do so. Non-portability aside for a moment, this can be
important... For instance, read section 2.2 of the following paper:

http://software.intel.com/file/27087
(this will result in the download of a PDF)

Sometimes, we need some way to align dynamically allocated memory on
specific boundaries...

Yes I agree, but how do you do it without stepping outside of Standard
C? (I've read that you cannot for C89 and C99.)

If one has these kinds of alignment concerns, I'd guess that there are
implementation extensions to address these concerns. In the PDF you
referenced, the Intel author(s) do '((int) p)' where 'p' is a 'void *'.
That's not portable, but it's their implementation. :)
 
S

Shao Miller

I was considering one approach to the age-old "opaque typedef"
problem. Say I have a type for dictionaries:

typedef struct Dict Dict;
Dict* dict_new(....);
void* dict_get(Dict* dict, void* key);

Now I want to have a separate type IntDict for dicts whose keys are
ints. I can use the implementation for Dict, I just want a distinct
type to ensure that other Dicts can't be passed to IntDict functions.
One approach is this:

typedef struct IntDict IntDict;

inline IntDict* intdict_new(....) {
return (IntDict*) dict_new(....);
}

inline void* intdict_get(IntDict* idict, int key) {
return dict_get((Dict*) idict,&key);
}

This seemed appealing to me, because we get a distinct type, and
IntDict* is directly a pointer type which is convenient for its
void*-compatibility.

But if the above doesn't work, I'm forced to resort to struct-wrapping
the actual pointer:

typedef struct IntDictP {
Dict* dict_;
} IntDictP;

inline IntDictP intdict_new(....) {
return (IntDictP) { dict_new(....) };
}

inline void* intdict_get(IntDictP idict, int key) {
return dict_get(idict.dict_,&key);
}

I think this is uglier, since now the user of the intdict API has to
use a special handle type instead of an ordinary pointer. But perhaps
this is then the only portable way to do this.

All right. Since preventing the API users from including internal
definitions is important, perhaps you could simply obfuscate your
pointers using arrays of 'char'.

typedef unsigned char (* p_dict)[1][1][1];
typedef unsigned char (* p_intdict)[1][1][1][1];

Inside your function implementations, you can simply convert the
pointers to the appropriate type.

Would that work?
 

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

No members online now.

Forum statistics

Threads
473,769
Messages
2,569,582
Members
45,071
Latest member
MetabolicSolutionsKeto

Latest Threads

Top