5

Consider the following C99 structure, ending with a flexible array member:

struct hdr
{
  size_t len;   
  size_t free;  
  char buf[];
};

len, for example, gets accessed with an inline function (to be put into a header file) like this, having buf as its argument:

static inline size_t slen(const char *s)
{
  struct hdr *h = (struct hdr*)(s - (int)offsetof(struct hdr, buf));
  return h->len;
}

This is part of a library, to be compiled with a C compiler. However, I would like to access this library from C++; this essentially means that the corresponding header file (with proper extern "C" {...} guard) must be valid C++ code. A possible solution is to define the slen function in the source code body, avoiding inline code completely, but this is not optimal.

My idea is to define a dummy C++ structure that is valid, and that I can map somehow to hdr, e.g.

struct cpp_hdr
{
  size_t len;
  size_t free;
  char buf[1];
}

Note that I only want to get correct (negative) offset values for len and free; no access to buf is intended.

Now my question: Is there any guarantee that

static inline size_t slen(const char *s)
{
  struct cpp_hdr *h = (struct cpp_hdr*)(s - (int)offsetof(struct cpp_hdr, buf));
  return h->len;
}

works, giving the same result?

Mohit Jain
  • 30,259
  • 8
  • 73
  • 100
lemzwerg
  • 772
  • 7
  • 15
  • 2
    There is no guarantee `buf` is at the same offset in the structure (Neither in C nor in C++), if it is a flexible-array-member, array-of-length-x or single member of element-type. In practice, it will be. But if you go that far, you might as well see whether that compiler accepts 0-length arrays. – Deduplicator Sep 17 '14 at 13:39
  • [Outdated but still relevant and related](http://stackoverflow.com/questions/4412749/are-flexible-array-members-valid-in-c) – Mohit Jain Sep 17 '14 at 13:41
  • Another observation: this is almost but not quite a Windows `BSTR`. – Cheers and hth. - Alf Sep 17 '14 at 13:44
  • Sorry about the comment about the cast, I didn't read the code correctly when I wrote that. **Kicking myself hard.** In addition, starting consumption of a cup of coffee. – Cheers and hth. - Alf Sep 17 '14 at 13:49
  • Is there a reason for this `(int)` conversion? – mafso Sep 17 '14 at 22:12
  • Thanks for all the useful answers. I haven't thought about compiling C and C++ with different compiler vendors; similarly I haven't thought about using the compiled library with different compilers. The library in question is [sds](https://github.com/lemzwerg/sds), putting the string management stuff into a separate library, which I try to make it work for my own project that needs both C and C++. – lemzwerg Sep 18 '14 at 04:16
  • @mafso: The `(int)` conversion was already in the code. – lemzwerg Sep 18 '14 at 04:17
  • If you have liberty to modify library code, you can `sds_start` and `sds_len` to non static exports and use sds_len to reliably return the length. – Mohit Jain Sep 18 '14 at 04:44
  • @mohit-jain: Exactly – I've stated this already in my original question :-) – lemzwerg Sep 18 '14 at 05:03
  • In my above comment regarding sds I forgot to mention that its git repository holds the string management stuff of [redis](http://redis.io/). – lemzwerg Sep 18 '14 at 05:05
  • See also: http://stackoverflow.com/questions/17424731/implementing-flexible-array-members-with-templates-and-base-class – M.M Nov 03 '14 at 20:41

2 Answers2

3

In the formal nothing is guaranteed, because C++ doesn't support flexible arrays: there is no such thing, no such syntax.

In practice compilers don't do things for no direct reason. So there won't be any willy-nilly padding introduced. However, just to make that abundantly clear I would use an array size of e.g. 666 instead of 1, which works better in the more general case (e.g. a small array of 1 char might be moved into some otherwise padding area in some other struct). As a benefit, clever code for allocation won't look simple any more. So that that has to be done properly.

All this said, it sure sounds like a 16-bit Windows BSTR, except a BSTR doesn't have that gap between the length and the string data. Consider whether this library is just someone's for no good reason reinvention of the wheel. If so, I suggest using an original wheel instead.

Cheers and hth. - Alf
  • 142,714
  • 15
  • 209
  • 331
  • Well, I guess that `BSTR` is a Windows-only thing, right? This doesn't help me... – lemzwerg Sep 18 '14 at 04:19
  • After some thinking I guess the right solution is to write autoconf support that simply computes the negative offsets to `len` and `free`, and those two constants are then used to transform a header template `hrd.h.in` to `hdr.h`, removing the use of `offsetof` in the header. – lemzwerg Sep 18 '14 at 04:22
  • Additionally, another autoconf macro (at usage time) could adjust the used data types of `len and `free` to have the proper size. – lemzwerg Sep 18 '14 at 04:32
2

Assuming library was compiled by a different compiler, it is not guaranteed that offset would be same. Practically, it should work most of the time.

If you are working on a specific compiler, you can use debugger and tune your offset calculation for that compiler and make an assertion in beginning to ensure your assumption (tuning) still holds true if later you decide to move to a different compiler or newer version.

Mohit Jain
  • 30,259
  • 8
  • 73
  • 100
  • 1
    THe question of binary level compatibility between different compilers (in the sense of compilers from different vendors), is a different one than asked. – Cheers and hth. - Alf Sep 17 '14 at 13:45
  • @Cheersandhth.-Alf By different compilers, my concern was decisions like padding, sizeof different types etc. – Mohit Jain Sep 17 '14 at 13:47