3

I am considering realizing a simple interface pattern in the C language.

A key characteristic is that it would provide multiple definitions for an opaque sturcture supplied by the interface's public header, that is, different implementations would provide different underlaying data for that structure (so across different translation units, the same structure would have different implementation).

I couldn't find any reference whether this would be a good or bad design pattern. At least it doesn't seem to violate the strict aliasing rule, only relying on the consistent order and padding for the common first element(s), which is guaranteed by the C standard (as far as I see). Of course I am aware of opject oriented patterns in C, but I couldn't see this particular one used.

Is this acceptable? Was such a pattern even used? (I couldn't find anything)

For easing understanding, following is a working example of three source files:

reader.h (The public interface definition)

#ifndef READER_H
#define READER_H

typedef struct reader_s reader_t;

char reader_read(reader_t* r);

#endif

reader.c (Glue logic for the interface)

#include "reader.h"

typedef char (reader_f)(reader_t*);

struct reader_s{
 reader_f* rfunc;
};

char reader_read(reader_t* r)
{
 return r->rfunc(r);
}

reader1.h (An implementation of the interface, header)

#ifndef READER1_H
#define READER1_H

#include "reader.h"

reader_t* reader1_get(void);

#endif

reader1.c (An implementation of the interface, code)

#include "reader1.h"

typedef char (reader_f)(reader_t*);

struct reader_s{
 reader_f* rfunc;
 int       pos;
};

static char reader1_read(reader_t* r);

reader_t reader1_obj = {&reader1_read, 0};

reader_t* reader1_get(void)
{
 return &reader1_obj;
}

static char reader1_read(reader_t* r)
{
 char rval = 'A' + (r->pos);
 (r->pos) = (r->pos) + 1;
 if ((r->pos) == 24){ (r->pos) = 0; }
 return rval;
}

main.c (Example usage)

#include "reader1.h"
#include <stdio.h>

int main(void)
{
 reader_t* rd = reader1_get();
 int       i;

 printf("\n");
 for (i = 0; i < 60; i++){
  printf("%c", reader_read(rd));
 }
 printf("\n");

 return 0;
}

Possibly the interface should provide another header file for implementers, providing the definition of the structure head containing the function pointers. Maybe it could even rather point to a class structure containing those to minimize object sizes for classes with many methods.

Jubatian
  • 2,171
  • 16
  • 22

1 Answers1

1

Nothing in the C Standard would define behavior in such cases, but if an implementation specifies the storage formats of data types and the means by which inter-module calls are handled, behavior could be defined as a consequence of that. Note, however, that some implementations with "link-time optimization" might not always perform inter-module calls in the "normal" documented fashion.

The only time I can see that such an approach might be workable would be if the public API associated with a type wanted to allow code to declare instances of the type directly or use the assignment operator with them, but did not otherwise want to commit to expose the field layout for any other purpose. In that case, having a public structure type containing something like a uint32[] or uint64[] might be a reasonable approach even if all code that would process the internals of the type used a "real" structure type. Such a design would avoid the need to recompile client code if the internal structure type changed, provided the size remained constant. On the other hand, I wouldn't consider any such design for code that might need to be processed with gcc-style type-based aliasing "optimizations" enabled, since gcc is willfully blind to places where such optimizations would break code.

supercat
  • 77,689
  • 9
  • 166
  • 211
  • Thanks! For the compiler I reasoned by that between different compilation units, the compiler has no memory (of how it transformed the structure), so to keep things working, it needs to be consistent with layout and padding. Also this is relevant: http://stackoverflow.com/questions/2748995/c-struct-memory-layout that the C standard enforces maintaining order. These together would imply that identical structure heads must compile to identical layout. Of course link time optimizations as you mentioned may still come in play. – Jubatian May 19 '17 at 11:37
  • @Jubatian: I find it implausible that the authors of the C89 would have gone through the trouble of detailing various aspects of layout if they didn't intend that pointers to things with identical layouts should be usable interchangeably, but compiler writers instead think that the authors of the Standard did such things for no reason. – supercat May 19 '17 at 14:19
  • If the compiler doesn't behave according to the standard when and where it states so, that's a compiler bug. Where's that bug tracker then? (there could be documented exceptions, though) – Jubatian May 20 '17 at 06:22
  • @Jubatian: When C89 was written, many things were defined as having compatible layouts as a way of specifying that pointer to one could be used to read the other. Unfortunately, rules were added that say that accessing objects via pointers of other types will yield Undefined Behavior, without adding exceptions that would allow code to make use of the layout guarantees defined elsewhere except via things like "memcpy". – supercat May 20 '17 at 23:14
  • @Jubatian: The authors of the Standard almost certainly expected that compiler writers would use common sense and attempt to recognize at least the most straightforward aliasing patterns that would exploit the layout guarantees, but it is more fashionable for compiler writers to claim that code which tries to exploit such guarantees is "broken". – supercat May 20 '17 at 23:16