C 개발자를 위한 Go 언어 강좌

By | 2025년 9월 13일
Table of Contents

C 개발자를 위한 Go 언어 강좌

1부: 시작하기

1장. Go 언어 소개

C와 Go의 철학적 차이

C의 철학:

  • 프로그래머에게 최대한의 제어권을 준다
  • 하드웨어에 가깝게, 최소한의 추상화
  • "프로그래머가 무엇을 하는지 안다"고 가정
  • 작고 단순한 언어

Go의 철학:

  • 단순성과 가독성을 최우선으로
  • 한 가지 일을 하는 한 가지 방법
  • 명확함이 영리함보다 낫다
  • 팀 협업과 대규모 소프트웨어에 최적화
  • 내장된 동시성 지원

Go는 C의 단순성을 계승하면서도, 현대적인 소프트웨어 개발의 요구사항을 반영했습니다.

Go의 주요 특징과 장점

C 개발자 관점에서 본 Go의 특징:

  1. 빠른 컴파일

    • C보다 훨씬 빠른 컴파일 속도
    • 의존성 관리가 명확해서 불필요한 재컴파일 없음
    • 대규모 프로젝트도 몇 초 안에 빌드
  2. 가비지 컬렉션

    • malloc/free 불필요
    • 메모리 누수와 이중 해제 걱정 없음
    • 하지만 C만큼의 세밀한 제어는 포기
  3. 내장 동시성

    • 고루틴: 경량 스레드 (스택 크기 ~2KB)
    • 채널: 안전한 통신 메커니즘
    • pthread보다 훨씬 사용하기 쉬움
  4. 정적 타입 + 타입 추론

    • C처럼 정적 타입의 안정성
    • 타입 추론으로 간결한 코드
  5. 표준 라이브러리

    • HTTP 서버, JSON 파싱, 암호화 등 내장
    • C는 외부 라이브러리 필요한 것들
  6. 단일 바이너리 배포

    • 정적 링크가 기본
    • 의존성 지옥 없음
    • 크로스 컴파일 간편

포기해야 하는 것들:

  • 매크로 (#define)
  • 포인터 산술 연산
  • 수동 메모리 관리의 완전한 제어
  • 헤더 파일
  • 전처리기

개발 환경 설정

1. Go 설치

Linux/macOS:

# 공식 사이트에서 다운로드
wget https://go.dev/dl/go1.25.3.linux-amd64.tar.gz
sudo tar -C /usr/local -xzf go1.25.3.linux-amd64.tar.gz

# PATH 설정 (~/.bashrc 또는 ~/.zshrc에 추가)
export PATH=$PATH:/usr/local/go/bin
export GOPATH=$HOME/go
export PATH=$PATH:$GOPATH/bin

Windows

가능하기는 하지만 매우 불편하다.

https://go.dev/ 에서 다운로드

추가설정

cgo 를 위한 gcc 설치

https://winlibs.com/ 에서 UCRT runtime 중 최신버전을 다운받아 적당한 폴더에 압축을 풀고 환경변수 PATH 에 bin 디렉토리를 추가해 줍니다.

확인:

go version
# go version go1.21.5 linux/amd64
go version
# go version go1.25.2 windows/amd64

2. 첫 프로젝트 만들기

# 프로젝트 디렉토리 생성
mkdir hello
cd hello

# 모듈 초기화 (C의 프로젝트 생성과 유사)
go mod init example.com/hello

# main.go 파일 생성

C 프로젝트와 다른 점:

  • Makefile 대신 go build 사용
  • 헤더 파일 없음
  • 의존성은 go.mod 파일로 관리

3. 에디터 설정

추천 에디터와 플러그인:

  • VS Code: Go (Go Team at Google)

필수 도구들:

# 코드 포맷터
go install golang.org/x/tools/cmd/goimports@latest

# 린터
go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest

Go는 코드 포맷팅 스타일을 강제합니다. C처럼 스타일 논쟁이 없습니다.

2장. 첫 Go 프로그램

Hello World

main.go

package main

import "fmt"

func main() {
    fmt.Println("Hello, World!")
}

빌드와 실행

# 빌드 (컴파일 + 링크 한번에)
go build

# 실행
./hello

# 또는 빌드하지 않고 바로 실행
go run main.go

2부: 기본 문법

3장. 변수와 타입

  • 변수 선언
var x int = 10
var name string = "John"
var pi float64 = 3.14

// 또는 타입 추론 사용
var x = 10        // int로 추론
var name = "John" // string으로 추론
var pi = 3.14     // float64로 추론
  • 전역변수와 지역변수
// 전역변수 (패키지 외부에서도 접근가능)
var GlobalCounter int = 0

// 전역변수 (패키지 레벨)
var globalName string = "Global"

// 여러 변수 동시 선언
var (
    MaxConnections int = 100
    DefaultTimeout int = 30
    ServerName string = "MyServer"
)

func main() {
    // 지역변수 (함수 내부)
    localVar := 10  // := 는 함수 내부에서만 사용 가능

    // 전역변수는 := 로 선언 불가
    // GlobalCounter := 100  // 컴파일 에러!

    GlobalCounter = 100  // OK: 전역변수 값 변경
}

// 대문자로 시작하면 다른 패키지에서도 접근 가능 (exported)
var ExportedVar int = 10  // public과 유사

// 소문자로 시작하면 같은 패키지 내에서만 접근 가능
var privateVar int = 10   // private과 유사
  • 기본 타입 비교
C 타입 Go 타입 설명
char byte (또는 uint8) 8비트 부호 없는 정수
int int 플랫폼 의존적 (32 또는 64비트)
long int64 64비트 정수
float float32 32비트 부동소수점
double float64 64비트 부동소수점
char* string 불변 문자열 (중요!)
rune UTF-8 문자 (int32의 별칭)
bool true/false
  • 타입 추론과 := 연산자
// 전통적인 방식
var x int = 10
var name string = "John"

// 타입 추론
var x = 10
var name = "John"

// 짧은 선언 (함수 내부에서만 사용 가능, 새로운 변수를 선언할 때만)
x := 10
name := "John"
pi := 3.14
  • 포인터: C와의 유사점과 차이점
// 유사점
x := 10
p := &x
*p = 20  // x가 20으로 변경됨

// 차이점
arr := [5]int{1, 2, 3, 4, 5}
p := &arr[0]
// p++     // 컴파일 에러!
// p += 2  // 컴파일 에러!

// NULL 대신 nil
var p *int = nil
type Person struct {
    Name string
    Age  int
}
p := &Person{}
p.Age = 30  // 자동으로 역참조됨 (*p).Age와 같음
p := new(int)    // *int 타입, 0으로 초기화됨
*p = 10
// 자동으로 메모리 해제
  • 타입 변환
var x int = 10
var y float64 = float64(x)  // 명시적 변환 필요

4장. 함수

  • 함수 선언과 호출
func add(a int, b int) int {
    return a + b
}

// 같은 타입 파라미터는 한 번만 명시 가능
func add(a, b int) int {
    return a + b
}

func printHello() {
    fmt.Println("Hello")
}

func main() {
    result := add(3, 5)
    printHello()
    // return 문 불필요 (main에서)
}
  • 다중 반환값 (C에는 없는 기능)
func divide(a, b int) (int, int) {
    quotient := a / b
    remainder := a % b
    return quotient, remainder
}

func main() {
    q, r := divide(10, 3)
    fmt.Printf("Quotient: %d, Remainder: %d\n", q, r)
    // 출력: Quotient: 3, Remainder: 1
}
  • Named return values
// 명명된 반환값 사용
func divide(a, b int) (quotient, remainder int) {
    quotient = a / b
    remainder = a % b
    return  // naked return (값 명시 불필요)
}
  • 가변 인자 함수
func sum(numbers ...int) int {
    total := 0
    for _, num := range numbers {
        total += num
    }
    return total
}

// 사용
result := sum(10, 20, 30)
result2 := sum(1, 2, 3, 4, 5)
result3 := sum()  // 인자 없이도 호출 가능
  • 함수를 값으로 다루기

Go에서 함수는 일급 객체(first-class citizen)입니다.

// 함수 타입 정의
type operation func(int, int) int

func add(a, b int) int { return a + b }
func subtract(a, b int) int { return a - b }

func calculate(a, b int, op operation) int {
    return op(a, b)
}

func main() {
    result := calculate(10, 5, add)
    result = calculate(10, 5, subtract)
}

5장. 제어 구조

  • if문 (선언문 포함 가능)

Go의 if문은 C와 비슷하지만 조건식을 괄호로 감싸지 않습니다. 대신 중괄호는 필수입니다.

if x > 10 {
    fmt.Println("크다")
}

조건식 안에서 변수 생성이 가능합니다.

package main

import (
    "fmt"
)

func getValue() int {
    return 0
}

func main() {
    if val := getValue(); val > 0 {
        fmt.Println("양수:", val)
    } else {
        fmt.Println("음수 또는 0:", val) // val은 여전히 유효
    }
}
  • for문 (Go의 유일한 반복문)

Go에는 for문만 존재합니다. while이나 do-while은 없습니다.

// C와 거의 동일하지만 괄호가 없음
for i := 0; i < 10; i++ {
    fmt.Println(i)
}

for {
    // 무한 반복
    if shouldStop {
        break
    }
}

range 키워드

// 배열/슬라이스 순회
numbers := []int{10, 20, 30, 40, 50}
for index, value := range numbers {
    fmt.Printf("numbers[%d] = %d\n", index, value)
}

// 인덱스만 필요한 경우
for i := range numbers {
    fmt.Println(i)
}

// 값만 필요한 경우 (인덱스 무시)
for _, value := range numbers {
    fmt.Println(value)
}
  • switch문 (break 불필요, fallthrough)
  • defer 문 (C에는 없는 기능)

함수가 종료되기 직전에 특정 코드를 실행하도록 예약하는 기능.
여러 개의 defer 문이 있으면 LIFO(Last In First Out) 순서로 실행.

func example() {
    defer fmt.Println("마지막")
    defer fmt.Println("두 번째")
    fmt.Println("첫 번째")
}
// 출력:
// 첫 번째
// 두 번째
// 마지막

3부: 메모리와 데이터 구조

6장. 포인터 심화

  • C 포인터와 Go 포인터의 차이
  • 포인터 산술 연산이 없는 이유
  • nil과 NULL
  • 포인터를 사용하는 시기

7장. 배열과 슬라이스

  • 배열과 슬라이스

    배열은 크기가 고정이다. 속도가 빠르다. 함수 전달시 포인터를 전달해야 한다.

    슬라이스는 크기를 변경할 수 있다. 참조 타입이기에 함수에 그대로 전달 가능하다.

// 배열 - 거의 사용하지 않음
var arr [5]int = [5]int{1, 2, 3, 4, 5}

// 슬라이스 - 일반적으로 사용
slice := []int{1, 2, 3, 4, 5}
slice = append(slice, 6)  // 동적으로 확장 가능

// 슬라이스 생성 방법들
s1 := make([]int, 5)        // 길이 5
s2 := make([]int, 0, 10)    // 길이 0, 용량 10
s3 := []string{"a", "b", "c"}
  • 슬라이스 내부 구조 이해
s1 := make([]int, 5)        // 길이 5
s2 := make([]int, 0, 10)    // 길이 0, 용량 10

용량을 지정하지 않는 경우, 용량이 부족하면 2배 크기의 슬라이스를 새로생성하고 값을 복사한다.

  • make와 new의 차이
구분 new make
적용 대상 모든 타입 slice, map, channel만
반환 타입 포인터 (*T) 참조 타입 (T)
초기화 제로값으로 초기화 사용 가능한 상태로 초기화
메모리 할당 메모리만 할당 내부 구조까지 초기화
p := new(Person)  // &Person{} 와 동일

참조타입을 반환하며, 함수 파라미터로 전달될 때에도 복사가 아닌 참조가 전달된다.

s := make([]int, 0, 10)
m := make(map[string]int)
ch := make(chan int)
  • append와 copy

8장. 맵과 구조체

  • 구조체
type Person struct {
    Name   string
    Age    int
    Height float64
}

// 리터럴로 초기화
p := Person{
    Name:   "Alice",
    Age:    30,
    Height: 165.5,
}

// 필드만 지정 (순서 무관)
p2 := Person{Age: 25, Name: "Bob"}

// 포인터로 접근 (-> 연산자 불필요!)
ptr := &p
ptr.Age = 31  // Go는 자동으로 역참조
// 타입 이름 없이 바로 사용
config := struct {
    Host string
    Port int
}{
    Host: "localhost",
    Port: 8080,
}

fmt.Println(config.Host)  // localhost
  • 메서드 추가하기
type Rectangle struct {
    Width  int
    Height int
}

// 값 리시버 (복사복을 실행)
func (r Rectangle) Area() int {
    return r.Width * r.Height
}

// 포인터 리시버 (실제 값을 변경)
func (r *Rectangle) Scale(factor int) {
    r.Width *= factor
    r.Height *= factor
}

// 사용
rect := Rectangle{Width: 10, Height: 5}
fmt.Println(rect.Area())  // 50

rect.Scale(2)
fmt.Println(rect.Area())  // 200
  • 맵(Map): 내장 해시테이블
// 선언과 초기화
ages := map[string]int{
    "Alice": 30,
    "Bob":   25,
    "Carol": 35,
}

// 추가/수정
ages["Dave"] = 40

// 조회
age := ages["Alice"]
fmt.Println(age)  // 30

// 삭제
delete(ages, "Bob")

// 존재 확인
age, exists := ages["Eve"]
if exists {
    fmt.Println("Eve's age:", age)
} else {
    fmt.Println("Eve not found")
}

4부: 메모리 관리

9장. 가비지 컬렉션

  • malloc/free에서 GC로
type Person struct {
    Name string
    Age  int
}

func createPerson(name string, age int) *Person {
    p := &Person{
        Name: name,
        Age:  age,
    }
    return p
    // free() 호출 불필요! GC가 자동으로 처리
}

func main() {
    p := createPerson("Alice", 30)
    // p를 사용
    // 더 이상 참조되지 않으면 GC가 자동으로 메모리 회수
}
  • 스택 vs 힙 할당

go 컴파일러가 스택(임시메모리) 에 저장할지 힙에 저장할지를 자동으로 선택합니다.

func stackAllocation() *int {
    x := 42
    return &x  // C에서는 위험! Go에서는 안전
}

func heapAllocation() *int {
    x := 42
    return &x  // 컴파일러가 x를 힙에 할당
}

func localOnly() {
    x := 42
    println(x)  // x는 스택에 할당 (함수 밖으로 나가지 않음)
}

10장. 메모리 안전성

  • 버퍼 오버플로우 방지

    언어레벨에서 오버플로우를 차단합니다.

arr := [5]int{1, 2, 3, 4, 5}
fmt.Println(arr[4])  // OK
// fmt.Println(arr[5])  // panic: runtime error: index out of range [5] with length 5

// 슬라이스도 마찬가지
slice := []int{1, 2, 3}
// value := slice[10]  // panic: runtime error: index out of range [10] with length 3
  • 댕글링 포인터 문제 해결
func noMoreDangling() *int {
    local := 42
    return &local  // Go 컴파일러가 자동으로 힙에 할당
}

func main() {
    ptr := noMoreDangling()
    fmt.Println(*ptr)  // 42 - 완전히 안전함!
}
  • 메모리 누수 방지
// 문제가 있는 코드
func processData(data []byte) []byte {
    // 큰 슬라이스에서 작은 부분만 필요한 경우
    result := data[0:10]  // 전체 배열을 참조하고 있음!
    return result
}

// 올바른 방법
func processDataCorrectly(data []byte) []byte {
    result := make([]byte, 10)
    copy(result, data[0:10])  // 필요한 부분만 복사
    return result
}
  • unsafe 패키지 (주의해서 사용하기)

    위와 같은 언어레벨 체크를 배제하기 위해 사용됩니다.
    성능이 극도로 중요한 부분에 제한적으로 사용되어야 합니다.

5부: 동시성

11장. 고루틴

언어레벨에서 스레드를 지원합니다.
go 키워드를 사용하는 것으로 스레드가 생성됩니다.
메인 함수가 종료됨과 동시에 스레드도 종료되므로 적절한 동기화를 해주어야 합니다.
(채널 챕터 참조)

package main

import (
    "fmt"
    "time"
)

func worker(id int) {
    fmt.Printf("Worker %d\n", id)
}

func main() {
    for i := 0; i < 10; i++ {
        go worker(i)  // 고루틴 생성은 단 한 줄!
    }

    time.Sleep(3 * time.Second)  // 고루틴이 끝나길 기다림 (임시 방편)
}

12장. 채널

"메모리를 공유해서 통신하지 말고, 통신해서 메모리를 공유하라"
(Don’t communicate by sharing memory; share memory by communicating)

  • 채널의 개념과 사용법

채널을 이요해 값을 전달하고 수신합니다.

package main

import "fmt"

func main() {
    // 채널 생성
    ch := make(chan int)

    // 고루틴에서 채널로 데이터 전송
    go func() {
        ch <- 42  // 채널에 값 보내기
    }()

    // 메인 고루틴에서 데이터 수신
    value := <-ch  // 채널에서 값 받기
    fmt.Println(value)
}
  • 버퍼드 채널 vs 언버퍼드 채널

언버퍼드 채널 – 동기 방식

package main

import "fmt"

func main() {
    ch := make(chan int)  // 버퍼 없음

    go func() {
        fmt.Println("고루틴: 데이터 전송 시작")
        ch <- 42  // 누군가 받을 때까지 블록됨
        fmt.Println("고루틴: 데이터 전송 완료")
    }()

    fmt.Println("메인: 데이터 수신 대기")
    value := <-ch  // 누군가 보낼 때까지 블록됨
    fmt.Printf("메인: 수신한 값 = %d\n", value)
}

버퍼드 채널 – 비동기 방식

package main

import (
    "fmt"
    "time"
)

func main() {
    ch := make(chan int, 3)  // 버퍼 크기 3

    // 버퍼가 가득 찰 때까지 블록되지 않음
    ch <- 1
    ch <- 2
    ch <- 3
    fmt.Println("3개의 값을 전송함")

    // 이 시점에서 ch <- 4를 하면 블록됨

    go func() {
        time.Sleep(time.Second)
        fmt.Println(<-ch)  // 1
        fmt.Println(<-ch)  // 2
        fmt.Println(<-ch)  // 3
    }()

    time.Sleep(2 * time.Second)
}
  • select 문

select는 여러 채널 작업을 동시에 대기할 수 있게 해줍니다.

package main

import (
    "fmt"
    "time"
)

func main() {
    ch1 := make(chan string)
    ch2 := make(chan string)

    go func() {
        time.Sleep(1 * time.Second)
        ch1 <- "one"
    }()

    go func() {
        time.Sleep(2 * time.Second)
        ch2 <- "two"
    }()

    // 두 채널 중 먼저 준비된 것을 처리
    for i := 0; i < 2; i++ {
        select {
        case msg1 := <-ch1:
            fmt.Println("received", msg1)
        case msg2 := <-ch2:
            fmt.Println("received", msg2)
        }
    }
}

13장. 동시성 패턴

  • Worker Pool 패턴
  • Pipeline 패턴
  • Fan-out, Fan-in
  • Context를 이용한 취소 처리
  • sync 패키지 (Mutex, WaitGroup 등)

6부: 실전 Go

14장. 에러 처리

  • errno에서 error 인터페이스로

Go에서 에러는 내장된 error 인터페이스로 표현됩니다.

type error interface {
    Error() string
}
  • 다중 반환값을 이용한 에러 처리

오류가 발생하면 발생한 오류를 함수를 호출한 곳으로 전달합니다.

package main

import (
    "fmt"
    "os"
)

func main() {
    // 파일 열기
    file, err := os.Open("data.txt")
    if err != nil {
        fmt.Fprintf(os.Stderr, "파일 열기 실패: %v\n", err)
        os.Exit(1)
    }
    defer file.Close()

    // 파일 작업 수행
    fmt.Println("파일을 성공적으로 열었습니다")
}
  • 오류 생성
package main

import (
    "errors"
    "fmt"
)

func example1() error {
    // 1. errors.New() - 간단한 에러 메시지
    return errors.New("뭔가 잘못되었습니다")
}

func example2(code int) error {
    // 2. fmt.Errorf() - 포맷팅된 에러 메시지
    return fmt.Errorf("잘못된 코드: %d", code)
}

func example3(filename string) error {
    // 3. 조건부 에러 반환
    if filename == "" {
        return errors.New("파일명이 비어있습니다")
    }
    return nil  // 에러 없음
}
  • panic과 recover

일반적으로 panic과 recover 는 거의 사용되지 않습니다.

panic 은 복구 불가능한 오류가 발생했을 때 사용되며, 오류 출력 후 프로그램을 종료시킵니다.

  • 에러 래핑과 언래핑
  • 커스텀 에러 타입

15장. 인터페이스

  • C에는 없는 Go의 인터페이스
package main

import (
    "fmt"
    "math"
)

type Shape interface {
    Draw()
    Area() float64
}

type Circle struct {
    Radius float64
}

// 메서드만 구현하면 자동으로 인터페이스 만족
func (c Circle) Draw() {
    fmt.Printf("Drawing circle with radius %f\n", c.Radius)
}

func (c Circle) Area() float64 {
    return math.Pi * c.Radius * c.Radius
}

// 사용
func main() {
    var s Shape = Circle{Radius: 5.0}
    s.Draw()
    fmt.Printf("Area: %f\n", s.Area())
}
  • 빈 인터페이스 (interface{})
// 모든 타입을 받을 수 있음 (C의 void*와 유사)
func printAnything(v interface{}) {
    fmt.Printf("Value: %v, Type: %T\n", v, v)
}

func main() {
    printAnything(42)           // int
    printAnything("hello")      // string
    printAnything(3.14)         // float64
    printAnything([]int{1,2,3}) // []int
}
  • 타입 추출

인터페이스의 실제값을 추출한다.
추출에 실패하면 런타임 오류가 발생한다.

package main

func main() {
    var a interface{} = 1

    i := a       // a와 i는 dynamic type이고 값은 1
    j := a.(int) // j는 int 타입이고, 값은 1

    println(i) // 포인터 주소 출력
    println(j) // 1 출력
}

16장. 패키지와 모듈

  • 헤더 파일 없는 세상

go 언어는 헤더 파일을 사용하지 않습니다.

// math/math.go
package math

func Add(a, b int) int {
    return a + b
}

func Subtract(a, b int) int {
    return a - b
}

// main.go
package main

import "example.com/hello/math"

func main() {
    println(math.Add(5, 3))
}

// go.mod
module example.com/hello

go 1.21.5
  • 패키지 구조화

    하나의 디렉토리 = 하나의 패키지

   myproject/
   ├── main.go          (package main)
   ├── utils/
   │   ├── string.go    (package utils)
   │   └── number.go    (package utils)
   └── database/
       └── db.go        (package database)
  • 공개/비공개 (대소문자 규칙)

첫 글자가 소문자인 경우, 패키지 내부에서만 접근가능합니다.

package calculator

// 공개 (Exported) - 첫 글자 대문자
func Add(a, b int) int {
    return a + b
}

// 비공개 (Unexported) - 첫 글자 소문자
func validate(n int) bool {
    return n >= 0
}

// 공개 변수
var MaxValue = 1000

// 비공개 변수
var threshold = 100

// 공개 타입
type Calculator struct {
    Name string    // 공개 필드
    version int    // 비공개 필드
}

// 비공개 타입
type internalState struct {
    count int
}
  • go modules와 의존성 관리

    C의 Makefile이나 CMakeLists.txt 대신, Go는 go.mod 파일로 의존성을 관리합니다.

go mod init github.com/username/myproject

go.mod

module github.com/username/myproject

go 1.21

require (
    github.com/gorilla/mux v1.8.0
    github.com/lib/pq v1.10.9
)

17장. 테스팅

  • 내장된 testing 패키지

    Go는 강력한 테스팅 프레임워크를 표준 라이브러리로 제공합니다.

    테스트 파일은 _test.go 접미사를 가집니다
    테스트 함수는 Test로 시작하고 *testing.T 파라미터를 받습니다
    별도의 main 함수나 테스트 러너가 필요 없습니다
    go test 명령어로 자동으로 모든 테스트를 찾아 실행합니다

  • 단위 테스트 작성하기

test\math.go

package test

func Add(a, b int) int {
    return a + b
}

func Multiply(a, b int) int {
    return a * b
}

test\math_test.go

package test

import "testing"

func TestAdd(t *testing.T) {
    result := Add(2, 3)
    expected := 5

    if result != expected {
        t.Errorf("Add(2, 3) = %d; expected %d", result, expected)
    }
}

7부: C와의 상호운용

18장. Cgo

  • Cgo란 무엇인가

    Cgo는 Go 프로그램에서 C 코드를 호출하거나, 반대로 C 프로그램에서 Go 코드를 호출할 수 있게 해주는 기능입니다.

  • C 코드를 Go에서 호출하기

C 코드 포함

import "C" 앞의 주석과 import 문 사이에 빈 줄이 있으면 안 됩니다.

https://pkg.go.dev/cmd/cgo

To use cgo write normal Go code that imports a pseudo-package "C". The Go code can then refer to types such as C.size_t, variables such as C.stdout, or functions such as C.putchar.
If the import of "C" is immediately preceded by a comment, that comment, called the preamble, is used as a header when compiling the C parts of the package. For example:

package main

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

void sayHello(const char* name) {
    printf("Hello, %s from C!\n", name);
    fflush(stdout);
}
*/
import "C"
import (
    "fmt"
    "unsafe"
)

func main() {
    name := C.CString("Gopher")
    defer C.free(unsafe.Pointer(name))

    fmt.Println("Hello from GO!")
    C.sayHello(name)
}

라이브러리 호출

calc.h

#ifndef CALC_H
#define CALC_H

double calc(double x, double y);

#endif

calc.c

#include <math.h>

double calc(double x, double y) {
    return sqrt(x * x + y * y);
}

main.go

package main

/*
#cgo CFLAGS: -I.
#cgo LDFLAGS: -lm
#include "calc.h"
*/
import "C"
import "fmt"

func main() {
    result := C.calc(3.0, 4.0)
    fmt.Printf("Distance: %.2f\n", float64(result))
}

19장. C 코드 마이그레이션

  • 마이그레이션 전략
  • 일반적인 변환 패턴
  • 주의할 점들
  • 실전 예제

8부: 고급 주제

20장. 성능 최적화

  • 프로파일링 도구
  • 벤치마킹 best practices
  • 메모리 최적화
  • 컴파일러 최적화 이해하기

21장. 표준 라이브러리 활용

  • io 패키지
  • fmt와 printf의 차이
  • net/http (웹 서버 만들기)
  • encoding/json
  • flag 패키지 (명령행 인자)

22장. 실전 프로젝트

  • CLI 도구 만들기
  • 간단한 웹 서버 구현
  • 시스템 프로그래밍 예제
  • C 프로젝트를 Go로 재작성하기

23장. 제네릭(Generics)

func Min[T int | float64](a, b T) T {
    if a < b {
        return a
    }
    return b
}

// 사용
result1 := Min[int](5, 3)           // 3
result2 := Min[float64](5.5, 3.2)   // 3.2
result3 := Min(5, 3)                // 타입 추론으로 [int] 생략 가능

커스텀 제약 정의

type Number interface {
    int | int64 | float64 | float32
}

func Sum[T Number](numbers []T) T {
    var sum T
    for _, n := range numbers {
        sum += n
    }
    return sum
}

답글 남기기