3

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.

Endophage
  • 19,217
  • 9
  • 54
  • 85
orls
  • 33
  • 3

2 Answers2

2

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?

JimB
  • 87,033
  • 9
  • 198
  • 196
  • Thanks -- I stumbled across the need to flush right after posting :-) . I don't fully understand _why_ c is buffering in this case, compared to 'normal' c: If I write the equivalent hello-world C program directly, using `puts`, and redirect it, I see output as expected. In that case, is my C compiler adding a flush for me or something? – orls Mar 06 '17 at 20:33
  • 1
    Did a bit of research; C will typically flush upon termination but cgo doesn't call C's `atexit` callbacks – issue [#4221](https://github.com/golang/go/issues/4221) suggests that was deliberate, but [other](https://github.com/golang/go/issues/5948) [issues](https://github.com/golang/go/issues/8808) leave it ambiguous. Apparently it's [been discussed](https://groups.google.com/forum/#!topic/golang-nuts/00xWO1lmzEc) but I can't find that discussion. `fflush` it is, then! – orls Mar 06 '17 at 23:01
  • @orls: yeah, that makes sense. I always figure that if you need to be sure the data was written, call fflush regardless. – JimB Mar 06 '17 at 23:05
0

The C library buffering is per line, so the first line can be left in the buffer before it is properly flushed (done at exit time in C programs). You can either try to flush stdout, or try adding a trailing \n in the first string. Does it work if you add the \n?

Giuseppe Scrivano
  • 1,025
  • 8
  • 11
  • Hah, I discovered this just after posting, and was about to answer myself, but you got here first! No: adding a newline makes no difference (`puts` adds one anyway). However: **yes**: flushing stdout makes a difference: `C.fflush(C.stdout)` at the relevant point worked – orls Mar 06 '17 at 20:27