1. 개념 요약

Go 문자열은 바이트 데이터의 주소와 길이로 구성된 불변 값이다. 내부에서는 {데이터 포인터, 길이} 형태의 두 워드로 표현된다.

문자열 연결(+)과 비교(==)는 컴파일 과정에서 런타임 함수 호출로 변환될 수 있다. go-os는 일반 운영체제용 Go 런타임을 연결하지 않으므로, gccgo가 참조하는 함수를 커널에서 구현해야 한다.

2. 왜 필요한가

바이트 배열로도 텍스트를 처리할 수 있지만 데이터와 길이를 따로 관리하고, 비교와 연결 절차도 직접 작성해야 한다. 문자열을 사용하면 데이터 주소와 길이가 하나의 값으로 관리되며 +== 연산자를 사용할 수 있다.

소스 코드에 작성한 a + ba == b는 컴파일 결과에서 런타임 함수 호출을 포함할 수 있다. freestanding 커널에 해당 함수가 정의돼 있지 않으면 링크 과정에서 undefined symbol 오류가 발생한다.

3. 등장 배경

이전 단계까지는 텍스트를 바이트 단위로 처리했고, 셸에서도 명령 이름을 바이트별로 비교했다. 문자열 연산을 추가하자 gccgo가 runtime.concatstrings 등의 런타임 심볼을 참조했다.

문자열을 연결하려면 결과를 저장할 새로운 메모리 영역이 필요하다. 따라서 heap 할당기를 구현한 뒤 문자열 런타임 함수를 추가했다.

4. 필요한 조건

조건설명
heap 할당기연결 결과를 저장할 메모리를 할당한다.
concatstrings여러 문자열을 연결하고 새로운 문자열 값을 반환한다.
memcmp길이가 같은 두 문자열의 바이트 데이터를 비교한다.
서술자16바이트 문자열 헤더 비교에 필요한 memequal128..f를 제공한다.

5. 핵심 구성 요소

구성 요소역할
문자열 헤더{데이터 포인터, 길이}. 문자열 값의 표현이다.
concatstrings여러 문자열을 순서대로 복사해 새로운 문자열을 만든다.
memcmp두 바이트열을 비교한다. ==가 호출한다.
memequal12816바이트 값(문자열 헤더)을 비교한다. 타입 서술자가 참조한다.

6. 동작 흐름

flowchart TD
    A["a + b"]
    B["gccgo가 concatstrings 호출"]
    C["조각 길이를 모두 더함"]
    D["heap에서 필요한 크기의 메모리 할당"]
    E["조각들을 차례로 복사"]
    F["새 문자열 헤더 반환"]

    A --> B --> C --> D --> E --> F

gccgo는 a + bconcatstrings 호출로 변환한다. concatstrings는 입력 문자열의 전체 길이를 계산하고, heap에서 결과를 저장할 공간을 할당한다. 이후 각 문자열의 데이터를 순서대로 복사하고 새로운 문자열 헤더를 반환한다. 입력 문자열은 변경되지 않는다.

7. 상태 변화

단계상태
연결 전각 문자열이 기존 데이터 영역을 가리킨다.
연결 중전체 길이만큼 메모리를 할당하고 입력 데이터를 복사한다.
연결 후새 문자열 헤더가 연결 결과를 가리키며 입력 문자열은 유지된다.

8. 코드나 설정에서 나타나는 형태

//go:linkname을 사용해 커널의 구현을 runtime.concatstrings 심볼에 연결한다. 이 구현은 임시 버퍼, 문자열 헤더 배열의 주소와 문자열 개수를 인자로 받는다.

//go:linkname concatstrings runtime.concatstrings
func concatstrings(buf unsafe.Pointer, strs *stringHeader, n int) string {
	// 조각 길이 합계만큼 heap에서 떼고 차례로 복사해 새 문자열을 만든다
}

문자열 비교는 먼저 길이를 확인하고, 길이가 같으면 memcmp로 바이트 데이터를 비교한다. 따라서 연결 함수와 별도로 memcmp 구현도 필요하다.

9. 자주 헷갈리는 지점

문자열 상수끼리 연결하면 gccgo가 컴파일 과정에서 결과를 미리 계산할 수 있어 concatstrings가 호출되지 않는다. 런타임 동작을 검증할 때는 입력 중 하나를 변수로 사용해야 한다.

concatstrings는 문자열 슬라이스를 인자로 받지 않는다. gccgo 12의 호출부는 문자열 헤더 배열의 주소와 원소 개수를 별도 인자로 전달한다. 이를 슬라이스 호출 규약으로 해석하면 인자 위치가 달라져 올바른 결과를 만들 수 없다.

64비트 환경에서 문자열 헤더는 16바이트이므로 타입 서술자가 memequal128을 참조한다. 8바이트 값에 사용하는 memequal64와는 별도의 구현이 필요하다.

10. 확인 방법

확인 대상확인 방법
연결변수와 리터럴을 이어 기대한 문자열이 되는지 본다.
비교연결 결과를 리터럴과 ==로 비교해 같다고 나오는지 본다.
길이연결한 문자열의 len이 두 조각 길이의 합인지 본다.

go-os는 부팅 자체 검증에서 "go" + 변수를 연결해 go-os가 되는지, len이 5인지, 리터럴과 같은지 확인하고 결과를 시리얼로 보낸다.

11. 다음에 볼 개념

문자열 연결과 비교를 구현한 다음에는 문자열 키를 사용하는 맵(map[string]int)으로 확장할 수 있다. 이를 위해서는 mapassign_faststr·mapaccess1_faststr와 문자열 해시·비교 서술자가 추가로 필요하다. 이후 셸 명령을 문자열 키로 등록하는 구조에 활용할 수 있다.

참고 자료