Strings and byte slices

The string type, which is a basic type in Go, does not exist in C. It has the char type, which represents a character, similar to Go's rune type, and strings are represented by an array of the char type, which is terminated with a .

The language makes it possible to declare character arrays directly as an array or as a string. The second declaration does not end the 0 value in order to end the string:

char lang[7] = {'G', 'o', 'l', 'a', 'n', 'g', ''};

char lang[] = "Golang";

We already saw how to convert a Go string to a C character array using the following function:

func C.CString(string) *C.char

This function will allocate the string in the heap so that it will be the application's responsibility to free such memory using the C.free function.

In order to convert a slice of bytes to a C character pointer named *char, we can use the following function:

func C.CBytes([]byte) unsafe.Pointer

As it happens, for C.CString, the application allocates the data in the heap and leaves the responsibilities of freeing it to the Go application.

The main difference between these two functions is that the first produces char[] , while the other creates *char. These two types are the equivalent of the Go string and []byte, since the bytes of the first type cannot be changed, while the one from the second type can.

There are a series of functions that are used to convert the C types back to Go ones. As far as strings are concerned, there are two functions: C.GoString creates a string from the entire array, and C.GoStringN enables the creation of a string using an explicit length:

func C.GoString(*C.char) string

func C.GoStringN(*C.char, C.int) string

To transform the C *char back to Go []byte, there is a single function:

func C.GoBytes(unsafe.Pointer, C.int) []byte

We can use the C.CBytes function to modify a slice of bytes using C and convert it back to a Go slice:

package main

/*
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

char* reverseString(char* s) {
int l = strlen(s);
for (int i=0; i < l/2; i++) {
char a = s[i];
s[i] = s[l-1-i];
s[l-1-i] = a;
}
return s;
}
*/
import "C"

import (
"fmt"
"unsafe"
)

func main() {
b1 := []byte("A byte slice")
c1 := C.CBytes(b1)
fmt.Printf("Go ptr: %p ", b1)
fmt.Printf("C ptr: %p ", c1)
defer C.free(c1)
c2 := unsafe.Pointer(C.reverseString((*C.char)(c1)))
b2 := C.GoBytes(c2, C.int(len(b1)))
fmt.Printf("Go ptr: %p ", b2)
fmt.Printf("%q -> %q", b1, b2)
}

Executing this application will show that when converting the byte slice, b1, to the C type as the c1 variable, it will change address. The C slice returned by the C function, c2, will have the same address as c1 because it is the same slice. When converted back to Go again and assigned to b2, it will have another address that is different from the initial Go byte slice, b1.

We can achieve the same result using the C string function. Let's use the same C code from the previous example and change the rest:

package main

/*
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

char* reverseString(char* s) {
int l = strlen(s);
for (int i=0; i < l/2; i++) {
char a = s[i];
s[i] = s[l-1-i];
s[l-1-i] = a;
}
return s;
}
*/
import "C"

import (
"fmt"
"unsafe"
)

func main() {
s1 := "A byte slice"
c1 := C.CString(s1)
defer C.free(unsafe.Pointer(c1))
c2 := C.reverseString(c1)
s2 := C.GoString(c2)
fmt.Printf("%q -> %q", s1, s2)
}

It is important to note that when transferring the Go string and bytes values to C, the values are copied. As a consequence, the C code is not capable of editing them directly, but will edit the copy, leaving the original Go value intact.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.145.194.57