C 언어 배열과 Go 언어 슬라이스 사용하기


이재홍 http://www.pyrasis.com 2014.12.17 ~ 2015.02.07



Go 언어의 슬라이스는 C 언어에서 배열로 사용할 수 있습니다. 그리고 C 언어의 배열을 Go 언어의 슬라이스로 만들 수 있습니다.

다음은 Go 언어의 슬라이스를 C 언어의 배열로 사용하는 방법입니다.

package main

/*
#include <stdio.h>

void CExample(void *p) {     // 슬라이스를 void * 타입으로 받음
	char *a = (char *)p; // char * 타입으로 변환

	printf("%c\n", a[0]); // H
	printf("%s\n", a);    // Hello, world!
}
*/
import "C"
import "unsafe"

func main() {
	b := []byte("Hello, world!")      // 바이트 형식으로 슬라이스 선언

	C.CExample(unsafe.Pointer(&b[0])) // 슬라이스 첫 번째 요소의 포인터를 unsafe.Pointer로 변환하여 넣어줌
}

실행 결과

H
Hello, world!

[]byte 형식으로 슬라이스를 선언한 뒤 슬라이스 첫 번째 요소의 레퍼런스(포인터)를 unsafe.Pointer로 변환하여 넘겨줍니다. 그리고 C 언어쪽에서는 void * 타입으로 받은 뒤 char * 타입으로 변환합니다. 변환한 뒤에는 배열처럼 사용하면 됩니다.

이번에는 C 언어의 배열을 Go 언어의 슬라이스로 만들어보겠습니다.

package main

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

#define ARRAY_LENGTH 5

int *GetArray()
{
	int *a = (int *)malloc(sizeof(int) * ARRAY_LENGTH); // 메모리 할당
	memset(a, 0, sizeof(int) * ARRAY_LENGTH);           // 0으로 초기화

	a[0] = 21; // 배열의 요소에 값을 넣어줌
	a[1] = -15;
	a[2] = 68;
	a[3] = 72;
	a[4] = -33;

	return a; // 할당한 메모리 리턴
}

int GetLength() // 배열의 길이를 구하는 함수
{
	return ARRAY_LENGTH;
}
*/
import "C"
import (
	"fmt"
	"reflect"
	"unsafe"
)

func main() {
	var a *C.int = C.GetArray()  // 배열의 포인터를 받음
	length := int(C.GetLength()) // 길이도 구함
	hdr := reflect.SliceHeader{  // 배열을 감싸는 슬라이스 헤더 생성
		Data: uintptr(unsafe.Pointer(a)), // unsafe.Pointer로 변환하여 넣어줌
		Len:  length,                     // 배열의 길이
		Cap:  length,                     // 배열의 길이
	}

	// 슬라이스 헤더 hdr은 []C.int, hdr의 레퍼런스는 포인터이므로 *[]C.int
	s := *(*[]C.int)(unsafe.Pointer(&hdr)) // 슬라이스 헤더의 포인터로 변환한 뒤 역참조
	fmt.Println(s)                         // [21 -15 68 72 -33]
	fmt.Println(s[2:5])                    // [68 72 -33]

	C.free(unsafe.Pointer(a)) // C 언어에서 malloc 함수로 할당한 메모리는 반드시 해제
}

먼저 C 언어 코드에서 malloc 함수로 메모리 공간을 할당한 뒤 0으로 초기화합니다.

int *a = (int *)malloc(sizeof(int) * ARRAY_LENGTH); // 메모리 할당
memset(a, 0, sizeof(int) * ARRAY_LENGTH);           // 0으로 초기화

Go 언어에서 슬라이스를 만들려면 배열 길이도 필요합니다. 따라서 다음과 같이 배열 길이를 구하는 함수도 만들어줍니다.

int GetLength() // 배열의 길이를 구하는 함수
{
	return ARRAY_LENGTH;
}

이제 Go 언어에서 배열의 포인터와 길이를 이용해 슬라이스로 만듭니다.

var a *C.int = C.GetArray()  // 배열의 포인터를 받음
length := int(C.GetLength()) // 길이도 구함
hdr := reflect.SliceHeader{  // 배열을 감싸는 슬라이스 헤더 생성
	Data: uintptr(unsafe.Pointer(a)), // unsafe.Pointer로 변환하여 넣어줌
	Len:  length,                     // 배열의 길이
	Cap:  length,                     // 배열의 길이
}

Go 언어의 슬라이스는 기본적으로 배열을 감싸고 있는 일종의 구조체입니다. 따라서 reflect.SliceHeader 함수로 배열을 감싸는 슬라이스 구조체(헤더)를 생성합니다. Data 필드에는 배열의 포인터 aunsafe.Pointer 타입으로 변환한 뒤 다시 uintptr 타입으로 변환하여 넣어줍니다. Len, Cap 필드에는 배열의 길이를 넣어줍니다.

슬라이스 헤더에서 슬라이스를 가져옵니다.

// 슬라이스 헤더 hdr은 []C.int, hdr의 레퍼런스는 포인터이므로 *[]C.int
s := *(*[]C.int)(unsafe.Pointer(&hdr)) // 슬라이스 헤더의 포인터로 변환한 뒤 역참조
fmt.Println(s)                         // [21 -15 68 72 -33]
fmt.Println(s[2:5])                    // [68 72 -33]

C.free(unsafe.Pointer(a)) // C 언어에서 malloc 함수로 할당한 메모리는 반드시 해제

처음 배열의 포인터는 *C.int 타입이었습니다. 이 포인터를 슬라이스 헤더로 만들면서 []C.int 타입으로 바꾸었습니다. 그리고 &hdr과 같이 헤더의 레퍼런스(포인터)를 구했으므로 []C.int 타입의 포인터인 *[]C.int가 됩니다. 이제 헤더 포인터를 unsafe.Pointer 포인터로 변환한 뒤 *[]C.int 타입으로 역참조하여 슬라이스를 가져오면 됩니다.

fmt.Println 함수로 출력해보면 슬라이스 형태로 출력이되며 부분 슬라이스도 만들 수 있습니다. 그리고 Go 언어의 슬라이스 형태가 되었지만 가비지 컬렉터에서 관리되지 않습니다. 따라서 슬라이스 사용 후 배열 a의 메모리는 반드시 C 언어의 free 함수로 해제해야 합니다.


저작권 안내

이 웹사이트에 게시된 모든 글의 무단 복제 및 도용을 금지합니다.
  • 블로그, 게시판 등에 퍼가는 것을 금지합니다.
  • 비공개 포스트에 퍼가는 것을 금지합니다.
  • 글 내용, 그림을 발췌 및 요약하는 것을 금지합니다.
  • 링크 및 SNS 공유는 허용합니다.

Published

01 June 2015