본문 바로가기
프로그래밍/Golang

Golang 변수와 Scope(스코프) 이해하기

by slowin 2024. 7. 19.

변수선언

var 키워드 사용

  • Golang에서는 var 키워드를 사용합니다.
  • `var {변수_이름} 타입` 의 형태를 따릅니다.
var name string
var age int
var isStudent bool

short variable declaration

  • Golang에서 타입추론이 가능합니다.
  • `:=`구문은 암묵적으로 타입 할당문을 사용할 수 있습니다.
  • `:=`구문은 함수 내에서만 사용할 수 있습니다. 패키지 레벨에서는 사용할 수 없습니다.

함수 내부에서 암묵적 유형의 선언 :=대신 짧은 할당 문을 사용할 수 있습니다.

name := "Gopher"
age := 25
isStudent := true

패키지 레벨 선언의 제한사항

  • 명확성과 일관성
    • Go는 코드의 명확성과 일관성을 매우 중요하게 여깁니다. := 연산자는 변수의 선언과 초기화를 동시에 수행하는 짧은 선언 문법입니다. 이 문법을 함수 내부로 제한함으로써, 전역 변수와 지역 변수의 선언 방식을 명확히 구분할 수 있습니다.
  • 언어 설계의 단순성
    • Go는 언어 자체를 단순하게 유지하려고 노력합니다. 패키지 레벨과 함수 레벨에서 다른 규칙을 적용함으로써, 전체적인 코드 가독성을 더 간단하게 만들 수 있습니다.
package main

var globalVar = "I'm global"  // 올바른 사용

// globalVar := "I'm global"  // 컴파일 에러

변수명만 선언

  • Go에서는 변수를 선언할 때 초기값을 지정하지 않으면, 해당 타입의 제로값(zero value)으로 자동 초기화됩니다.
  • 이 방식은 변수를 선언만 하고 나중에 값을 할당하고자 할 때 유용합니다.
  • Go의 제로값 초기화 특성 덕분에 변수를 사용하기 전에 반드시 초기화할 필요가 없어 편리합니다.
  • 주의: Go에서는 변수명만 선언하고 타입을 생략하는 것은 불가능합니다.
var name string    // 빈 문자열 ""로 초기화
var age int        // 0으로 초기화
var isStudent bool // false로 초기화
var f float64      // 0.0으로 초기화
var ptr *int       // nil로 초기화

여러 변수 동시 선언

예제

  • 타입 일치: 같은 타입의 변수들을 선언할 때는 타입을 한 번만 명시
  • 병렬 할당: 여러 변수에 동시에 값을 할당할 수 있습니다
// 타입 일치: 같은 타입의 변수들을 선언할 때는 타입을 한 번만 명시
var a, b, c int
a, b, c = 1, 2, 3

// 병렬 할당: 여러 변수에 동시에 값을 할당
var x, y int
x, y = 10, 20
x, y = y, x // 값 교환

블럭과 스코프

블럭

블록이란?

블록은 중괄호 { } 로 둘러싸인 코드의 부분입니다.

{
    // 이 안은 하나의 '방'입니다.
}

블록의 특징

  1. 독립된 공간: 각 블록은 자신만의 독립된 공간입니다.
  2. 규칙이 있는 공간: 블록 안에서 만든 규칙(변수)은 그 블록 안에서만 통해요.
  3. 중첩 가능: 블록 안에 또 다른 블록을 만들 수 있어요. 방 안에 작은 방을 만드는 것과 비슷해요.
func 집() {
    var 거실물건 = "TV"
    {
        // 이것은 '안방'이에요
        var 안방물건 = "침대"
        fmt.Println(거실물건)  // TV 출력 가능
        fmt.Println(안방물건) // 침대 출력 가능
    }
    fmt.Println(거실물건)  // TV 출력 가능
    // fmt.Println(안방물건) // 오류! 안방물건은 안방에서만 사용 가능해요
}

패키지 레벨 스코프

  • 패키지 레벨에서 선언된 변수는 해당 패키지의 모든 파일에서 접근 가능합니다.
package main

var globalVar = "I'm global"

func main() {
    // globalVar 사용 가능
}

함수 레벨 스코프

  • 함수 내에서 선언된 변수는 해당 함수 내에서만 접근 가능합니다.
func exampleFunction() {
    localVar := "I'm local"
    // localVar는 이 함수 내에서만 사용 가능
}

블록 레벨 스코프

  • 중괄호 {} 내에서 선언된 변수는 해당 블록 내에서만 접근 가능합니다.
func blockScopeExample() {
    if x := 10; x > 5 {
        y := 20
        // x와 y 모두 사용 가능
    }
    // 여기서는 x와 y 모두 사용 불가
}

변수 섀도잉

  • Go에서는 내부 스코프에서 외부 스코프와 같은 이름의 변수를 선언할 수 있습니다. 이를 변수 섀도잉이라고 합니다.
  • 참고: shadowing-variables
func shadowingExample() {
    x := 10
    if true {
        x := 20 // 외부의 x를 가림
        fmt.Println(x) // 20 출력
    }
    fmt.Println(x) // 10 출력
}

변수의 수명

변수의 수명은 그 변수가 메모리에 존재하는 기간을 의미합니다.

  • 지역 변수: 함수 호출이 끝나면 소멸
  • 전역 변수: 프로그램이 종료될 때까지 존재

예제를 통해 힙메모리 변화를 살펴 보면서 변수의 수명을 테스트 해보겠습니다.

package main

import (
	"fmt"
	"runtime"
	"time"
)

type exampleStruct struct {
	data string
}

func main() {
	fmt.Println("프로그램 시작")

	// 초기 메모리 통계
	printMemStats("초기 상태")

	// 많은 객체 생성
	createObjects()

	// 가비지 컬렉션 전 메모리 통계
	printMemStats("객체 생성 후")

	// 가비지 컬렉션 실행
	runtime.GC()

	// 가비지 컬렉션 후 메모리 통계
	printMemStats("가비지 컬렉션 후")

	fmt.Println("프로그램 종료")
}

func createObjects() {
	var objects []*exampleStruct
	for i := 0; i < 1000000; i++ {
		objects = append(objects, &exampleStruct{data: "some data"})
	}
	fmt.Println("1,000,000개의 객체 생성됨")
	// objects는 이 함수가 종료되면 접근 불가능해짐
}

func printMemStats(stage string) {
	var m runtime.MemStats
	runtime.ReadMemStats(&m)
	fmt.Printf("\n--- 메모리 통계 (%s) ---\n", stage)
	fmt.Printf("Alloc = %v MiB\n", bToMb(m.Alloc))
	fmt.Printf("TotalAlloc = %v MiB\n", bToMb(m.TotalAlloc))
	fmt.Printf("Sys = %v MiB\n", bToMb(m.Sys))
	fmt.Printf("NumGC = %v\n", m.NumGC)
	time.Sleep(time.Second) // GC에 시간을 주기 위한 지연
}

func bToMb(b uint64) uint64 {
	return b / 1024 / 1024
}
프로그램 시작

--- 메모리 통계 (초기 상태) ---
Alloc = 0 MiB
TotalAlloc = 0 MiB
Sys = 6 MiB
NumGC = 0
1,000,000개의 객체 생성됨

--- 메모리 통계 (객체 생성 후) ---
Alloc = 30 MiB
TotalAlloc = 58 MiB
Sys = 56 MiB
NumGC = 7

--- 메모리 통계 (가비지 컬렉션 후) ---
Alloc = 0 MiB
TotalAlloc = 58 MiB
Sys = 56 MiB
NumGC = 8
프로그램 종료

  • Alloc: 현재 할당된 힙 메모리
  • TotalAlloc: 프로그램 시작 이후 총 할당된 메모리
  • Sys: 시스템에서 얻은 총 메모리
  • NumGC: 가비지 컬렉션 실행 횟수

결과:

  • 객체 생성 후 Alloc 값이 크게 증가합니다.
  • 가비지 컬렉션 실행 후 Alloc 값이 감소합니다.
  • NumGC 값이 증가하여 가비지 컬렉션이 실행되었음을 확인할 수 있습니다.

정리

Go 언어에서 변수와 스코프를 제대로 이해하고 활용하면 더 효율적이고 안전한 코드를 작성할 수 있습니다. 변수의 가시성을 적절히 제어하고, 필요한 곳에서만 접근할 수 있도록 하는 것이 중요합니다. 독립적인 블록의 사용은 가능하지만, 대부분의 경우 불필요하며 더 명확한 코드 구조를 위해 함수, 메서드, 제어문 등을 활용하는 것이 좋습니다.