Go crash - misbehave between C and GO

Hi,

the code down use the C operator &xx->out to access the FIRST entry of an embedded struct and GO use &xx.out for the same task → the problem is that GO evaluate xx and C not. If you look to the assembler: https://godbolt.org/z/pK6d8v the C compiler translate the &xx->out into a simple pointer-cast WITHOUT any overhead and GO seems to ignor the fact.

The following C code works fine:

#include <stdlib.h>
#include <stdio.h>

struct base {
    int b;
};

struct A {
    struct base obj;
    int a;
};

void method_base (struct base* hdl, int val) {
    if (hdl == NULL) {
      fprintf(stderr,"sorry.\n");
    } else {
      hdl->b = val;
      fprintf(stderr,"set: hdl->b = %d\n", hdl->b);
    }
}

int main(int argc, char** argv) {

    fprintf(stderr,"init: aO = calloc\n");
    struct A * aO = (struct A*) calloc(1,sizeof(*aO));
    fprintf(stderr,"init: bO = NULL\n");
    struct A * bO = NULL;

    fprintf(stderr,"step: cast aO\n");
    method_base((struct base*) aO, 1);
    fprintf(stderr,"step: ref aO\n");
    method_base(&aO->obj, 2);
    fprintf(stderr,"step: cast bO\n");
    method_base((struct base*) bO, 3);
    fprintf(stderr,"step: ref bO\n");
    method_base(&bO->obj, 4);
    fprintf(stderr,"step: end.\n");
}

OUTPUT:

init: aO = calloc
init: bO = NULL
step: cast aO
set: hdl->b = 1
step: ref aO
set: hdl->b = 2
step: cast bO
sorry.
step: ref bO
sorry.
step: end.

the following GO code crash

package main

// #include <stdlib.h>
// #include <stdio.h>
// struct base {
//     int b;
// };
// struct A {
//     struct base obj;
//     int a;
// };
// void method_base (struct base* hdl, int val) {
//     if (hdl == NULL) {
//       fprintf(stderr,"sorry.\n");
//     } else {
//       hdl->b = val;
//       fprintf(stderr,"set: hdl->b = %d\n", hdl->b);
//     }
// }
import "C"
import "fmt"
import "unsafe"
import "os"

func main() {

    fmt.Fprintf(os.Stderr,"init: aO = calloc\n");
    aO := (*C.struct_A)(C.calloc(1,C.sizeof_struct_A));
    fmt.Fprintf(os.Stderr,"init: bO = NULL\n");
    bO := (*C.struct_A)(C.NULL);

    fmt.Fprintf(os.Stderr,"step: cast aO\n");
    C.method_base((*C.struct_base)(unsafe.Pointer(aO)), 1);
    fmt.Fprintf(os.Stderr,"step: ref aO\n");
    C.method_base(&aO.obj, 2);
    fmt.Fprintf(os.Stderr,"step: cast bO\n");
    C.method_base((*C.struct_base)(unsafe.Pointer(bO)), 3);
    fmt.Fprintf(os.Stderr,"step: ref bO\n");
    C.method_base(&bO.obj, 4);
    fmt.Fprintf(os.Stderr,"step: end.\n");
}

OUTPUT:

init: aO = calloc
init: bO = NULL
step: cast aO
set: hdl->b = 1
step: ref aO
set: hdl->b = 2
step: cast bO
sorry.
step: ref bO
panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x1 addr=0x0 pc=0x48fbb4]

goroutine 1 [running]:
main.main()
…/main.go:39 +0x224

Looks like there is an answer in golang-nuts by Ian Lance Taylor: https://groups.google.com/forum/#!msg/golang-nuts/sQ0NJBnpM9c/GKpn7_L9AwAJ

(copied here for convenience:)

C lets you take the address of an offset of a NULL pointer.
Go does not let you take the address of an offset of a nil pointer, even if the offset is zero.

Hi, @aotto1968, I see what you mean, but it’s been my experience that Go has (to me) an idiosyncratic handling of NULL/nil pointers (consider this).

That being said, I’m curious why it’s an issue for you.

the issue is that …

  1. I want to avoid “casting” of pointers… and
  2. the application logic change if NULL-Pointer-test go into “caller”

Isn’t it a good idea to change the application logic anyway? Consider if you (or someone else) refactored struct A to add a header. The offset of obj then becomes 4 or 8 and the NULL check fails to detect the error.

The “obj” have to be the first in struct → it is an “object-model” and the goal is that both pointers “A” and “base” are the same. if you like c++ language than “base” is the “base-class” of “A” and the “method_base” can be used with a “base” pointer as well with a “A” pointer.

Based on your example and your mention of an object model, it sounds like you are trying to port an inheritance-based object-oriented object model to Go. I think you either need to consider a different language or you will need to refactor your code significantly because Go intentionally omits inheritance from the language.