Search code examples
gocgo

Using cgo, why does C output not 'survive' piping when golang's does?


I'm experimenting with cgo to use C code from golang, but in my little hello-world test, I've ran into something I can't understand or find more information about.

I'm starting with a simple test similar to examples I've found

    package main

    import (
        "fmt"
        "unsafe"
    )

    /*
    #import <stdio.h>
    #import <stdlib.h>
    */
    import "C"

    func main() {
        go2c := "Printed from C.puts"
        var cstr *C.char = C.CString(go2c)
        defer C.free(unsafe.Pointer(cstr))
        C.puts(cstr)
        fmt.Printf("Printed from golang fmt\n")
    }

This simple example just echoes strings to stdout from both golang (using fmt.Printf) and raw C (using C.puts) via the basic cgo binding.

When I run this directly in my terminal, I see both lines:

    $ ./main
    Printed from C.puts
    Printed from golang fmt

When I run this but redirect output in any way – pipe to less, shell redirection to a file, etc – I only see golang's output:

    ./main | cat
    Printed from golang fmt

What happens to the C.puts content when piping / redirecting?

Secondary questions: Is this a cgo quirk, or a c standard library quirk I'm not aware of? Is this behaviour documented? How would I go about debugging this on my own (e.g. is there a good/plausible way for me to 'inspect' what FD1 really is in each block?)

Update: If it's relevant, I'm using go version go1.6.2 darwin/amd64.


Solution

  • This is C behavior you're seeing.

    Go does not buffer stdout, while in C it is usually buffered. When the C library detects stdout is a tty, it may use line buffering, so the additional \n inserted by puts will cause the output to be displayed.

    You need to flush stdout to ensure you get all the output:

    go2c := "Printed from C.puts"
    var cstr *C.char = C.CString(go2c)
    defer C.free(unsafe.Pointer(cstr))
    C.puts(cstr)
    C.fflush(C.stdout)
    fmt.Printf("Printed from golang fmt\n")
    

    See also

    Why does printf not flush after the call unless a newline is in the format string?

    Is stdout line buffered, unbuffered or indeterminate by default?