3

I am trying to initialize a struct of C array in go side. I am new to cgo. Still trying to understand the use case.

test.h



typedef struct reply {
  char     *name;
  reply_cb  callback_fn;
} reply_t;

typedef struct common {
  char      *name;
  int        count;
  reply_t    reply[];
} common_t;


int
init_s (common_t *service);


test.go


    name := C.CString("ABCD")
    defer C.free(unsafe.Pointer(name))

    num := C.int(3)

    r := [3]C.reply_t{{C.CString("AB"), (C.s_cb)(unsafe.Pointer(C.g_cb))},
                       {C.CString("BC"), (C.s_cb)(unsafe.Pointer(C.g_cb))},
                       {C.CString("CD"), (C.s_cb)(unsafe.Pointer(C.g_cb))}}

    g := C.common_t{
        name: name,
        count: num,
        reply : r,
    }

    rc := C.init_s(&g)

I am getting error on "reply: r" unknown field 'r' in struct literal of type

Any help will be appreciated. The goal is initialize and then use it values in C init_s for processing.

kostix
  • 51,517
  • 14
  • 93
  • 176
Kakarott
  • 73
  • 2
  • Your example is not a [mre] because it's not *reproducible*, but I strongly suspect you're running afoul of the C language rule that a flexible array member cannot be statically initialized. See [this related question](https://stackoverflow.com/q/27852062/1256452) which mentions the GCC extension that makes GNU C allow it. – torek Sep 08 '22 at 23:27
  • @torek, I think the problem is subtler because that GCC extension is about interpreting C code constructs, and here the struct field is attempted to be initialized dynamically (from Go code) ;-) – kostix Sep 09 '22 at 09:59

1 Answers1

3

You cannot use a flexible array field from Go: https://go-review.googlesource.com/c/go/+/12864/.

I think the reasonong is simple: this wart of C normally requires you to perform a trick of allocating a properly-aligned memory buffer long enough to accomodate for the sizeof(struct_type) itself at the beginning of that buffer plus sizeof(array_member[0]) * array_element_count bytes. This does not map to Go's type system because in it, structs have fixed size known at compile time. If Go would not hide reply from the definition, it would refer to a zero-length field you cannot do anything useful with anyway—see #20275.

Don't be deceived by code examples where a flexible array member field is initialized with a literal: as torek pointed out, it's a GCC extension, but what is more important, it requires work on part of the compiler—that is, it analyzes the literal, understands the context it appeared in and generates a code which allocates large enough memory block to accomodate both the struct and all the members of the flexible array.
The initialization of the array in your Go code may look superficially similar but it has an important difference: it allocates a separate array which has nothing to do with the memory block of the struct it's supposed to be "put into".
What's more Go's array are different beasts than C's: in C, arrays are pointers in disguise, in Go, arrays are first-class citizens and when you assign an array or pass it to a function call, the whole array is copied by value—as opposed to "decaying into a pointer"—in C's terms. So even if the Go compiler would not hide the reply field, assignment to it would fail.

I think you cannot directly use values of this type from Go without additional helper code written in C. For instance, to initialize values of common_t, you would write a C helper which would first allocate a memory buffer long enough and then expose to the Go code a pair of pointers: to the beginning of the buffer (of type *C.common_t), and to the first element of the array—as *C.reply_t.

If this C code is the code you own, I'd recommend to just get rid of the flexible array and maintain a pointer to a "normal" array in the reply field. Yes, this would mean extra pointer chasing for the CPU but it will be simpler to interoperate with Go.

torek
  • 448,244
  • 59
  • 642
  • 775
kostix
  • 51,517
  • 14
  • 93
  • 176
  • Technically a C array is an array: it's not a "pointer in disguise" so much as the fact that arrays "break apart" (*becoming* pointers—the usual term here is "decay" as you noted) the moment you handle them other than with special tongs or forceps or whatever. But in any case, F.A.M.s do require use of C malloc() so the OP would indeed need extra helpers here. – torek Sep 09 '22 at 16:30
  • @torek, yeah, I tried to come up with a terse explanation highlighting the fact there's no way to make the `=` operator in C copy the array's data, while in Go it does precisely that, but failed :-) – kostix Sep 09 '22 at 16:50
  • Hello, Thank you for your example. I understand the need for C helper function for initialization. However, I am not clear on the C.reply_t. Indexing is not supported in Go. I found a similar example in stackoverflow https://stackoverflow.com/questions/42841793/cgo-using-c-struct-array-and-assign-value-in-go So is it possible to achieve profiles.profile[0].profileId = C.int(2) will it better if these assignment are done via another C helper function in C side rather than go side? – Kakarott Sep 09 '22 at 22:07
  • @Kakarott, I'm not sure I've quite parse your comment. Indexing (of arrays and slices) is definitely supported in Go. Indexing of C arrays _from Go_ is not supported directly, but that's because there's no Go data type (and associated syntax) which would match C's arrays 1-to-1. But there are techniques allowing to do that anyway, with the arguably most useful is making a Go slice out of C array and indexing the former. – kostix Sep 10 '22 at 17:54