function parameter type compatibility

Viewed 77

I'm hiding some struct fields from struct types to make public API headers more clear.

First I used this style to hide (not actually hide, just separate from public editable members) some members,

include/scene.h

typedef struct ScenePrivateFields {
  SceneFlags flags;
  /* other members */
} ScenePrivateFields;

typedef struct Scene {
  ScenePrivateFields _priv;
  RootNode           rootNode;
  /* other members */
} Scene;

with this style I always use Scene type in function parameters then accessing private fields using _priv member. But keeping all private fields in public header makes header more complex

Eventually I switched to another style

include/scene.h

typedef struct Scene {
  RootNode rootNode;
  /* other members */
} Scene;

EXPORT
Scene*
allocScene(void);

src/types/impl_scene.h

typedef struct SceneImpl {
  Scene      pub;
  SceneFlags flags;
  /* other members */
} SceneImpl;

for instance if I have a function like this:

include/scene.h

void
renderScene(Scene * __restrict scene, /* other params */);

I have to cast scene to SceneImpl to access private fields. I'm doing this like:

src/scene/scene.c

void
renderScene(Scene * __restrict scene, /* other params */) {
  SceneImpl *sceneImpl;

  sceneImpl = (SceneImpl *)scene;
}

to avoid casting every function call I thought that maybe I can do something like this, if it is legal and not violating C standards:

src/scene/scene.c

void
renderScene(SceneImpl * __restrict sceneImpl, /* other params */) {
  /* skip casting scene to sceneImpl */
}

Since Scene is first member of SceneImpl, Can I define public api (function) with Scene and define implementation (function) with SceneImpl. I think it would work because both are pointer, is it valid or good idea?

NOTE: I'm compiling with -fstrict-aliasing

EDIT: FWIW, Here alloc function implementation, users must use this func to alloc struct:

EXPORT
Scene*
allocScene(void) {
  SceneImpl *sceneImpl;

  sceneImpl = calloc(1, sizeof(*sceneImpl));

  /* initialize pulic part */
  sceneImpl->pub.field1 = ...

    /* initialize private part */
  sceneImpl->priv_field1 = ...

  /* return public part */
  return &sceneImpl->pub;
}
2 Answers

You can use an opaque type for the private potion of the data.

In your public header, define your structures as follows:

// forward declaration of struct ScenePrivateFields 
typedef struct ScenePrivateFields ScenePrivateFields;

typedef struct Scene {
  ScenePrivateFields *_priv;    // now a pointer
  RootNode           rootNode;
  /* other members */
} Scene;

Then in your implementation file, you define the private struct:

struct ScenePrivateFields {
  SceneFlags flags;
  /* other members */
}

You'll also need to define a function that dynamically creates an instance of struct ScenePrivateFields and return a pointer to it to populate the pointer in the public struct.

The language which Dennis Ritchie designed and called C defined many behaviors in terms of physical storage layouts. This made Ritchie's language superior to many other existing languages for purposes like systems programming, since it meant that the language could be used to implement many kinds of data structures beyond those imagined by its creator.

If a compiler makes a bona fide effort to support the semantics of Dennis Ritchie's language, it should have no trouble supporting the constructs you describe. Unfortunately, when writing a rule that was (according to the published rationale) supposed to allow compilers to make certain optimizations in most cases where there would be no reason to expect that they would change a program's semantics, they wrote it in a way that compiler writers have regarded as an invitation to ignore obvious evidence that such optimizations would change program semantics.

If there is no need to have "user" code see a completed structure type, it's possible to have a header file simply say typedef struct tagName typeName; and then use pass around pointers of type typeName*. Unfortunately, it may sometimes be necessary to do things that would require that a full type be available even if user code wouldn't need to actually access most of the members thereof. If that is necessary, I would suggest that it is better to use whatever compiler option(s) would be necessary to tell a compiler that it should allow for the possibility that a pointer of one structure type may be used to access members of another which has a suitable layout [-fno-strict-aliasing on gcc or clang], than to work around compiler writers' desire to process a gutted version of the language with such useful abilities removed.

Related