Golang 기초 (9) : 구조체에 대하여

Eon Kim·2022년 1월 16일
1

Golang 기초 

목록 보기
9/14
post-thumbnail

안녕하세요, 주니어 개발자 Eon입니다.

이번 포스트에서는 구조체에 대해 다루겠습니다.

📝 구조체란?

Structure 라고 한다.

  • 여러 필드를 묶어둔 타입


위의 그림을 보면, 직원 한 명을 특정하기 위해 총 8개의 속성이 사용됐습니다.
구조체를 어떻게 사용하는지 아직은 모르지만, 저 속성들 모두 사용해야 한다면 아래와 같이 사용할 수 있습니다.

var Employee1_Name string = "Eon"
var Employee1_Rank string = "staff"
var Employee1_No uint32 = 200803
var Employee1_Department string = "Development"
var Employee1_Vacation int8 = 15
var Employee1_AnnualIncome uint64 = 50000000
var Employee1_Email string = "vamos_eon@email.com"
var Employee1_InCharge string = "backend"

정말 복잡합니다. 최대한 가독성 좋게 만들어 보려고 해도 표현할 직원이 몇이나 더 생길지도 모르고, 사용할 때마다 사이드 이펙트를 고려해야 합니다.
가령, Employee1의 이름을 다른 사람으로 바꾼다면 그건 전혀 의미가 없는 정보가 됩니다.
따라서 무엇 하나 바꾼다면 그게 다른 속성에 영향을 끼치는지 확인 후에 전부 함께 바꿔야 합니다.
앞에 Employee1_를 항상 써가면서 말입니다.

구조체를 사용하면 위의 그림처럼, 직원마다 다른 정보를 넣어 관리하기 편리해집니다.



📝 구조체

구조체로 표현하면 값을 바꿀 때도, 새로 값을 넣을 때도 편하게 넣을 수 있습니다.


📌 구조체의 선언

type 타입명 struct {
	필드명 타입
 	...
	필드명 타입
}

작성할 구조체의 이름을 정합니다.
구조체의 이름은 변수를 선언할 때 작성하는 타입과 비슷하게 작성할 수 있습니다.

위의 사용법을 바탕으로 직원에 대한 구조체를 작성해보겠습니다.

type Employee struct {
	Name		string
	Rank		string
	No		uint32
	Department	string
	Vacation	int8
	AnnualIncome	uint64
	Email		string
	InCharge	string
}

구조체를 작성했습니다.


📌 구조체 변수의 선언

var 구조체변수명 구조체타입명

이어서, 구조체 변수를 선언하겠습니다.

var employee Employee

이제 구조체 변수 employee은 구조체 Employee가 가지는 필드를 모두 사용할 수 있습니다.


📌 구조체 변수의 초기화

위에서 선언한 구조체 변수 employee에 값을 초기화해서 사용해보겠습니다.
구조체 변수에 값을 대입하기 위해서는 아래의 방법들이 있습니다.

📍 방법 1. 각 필드를 따로 초기화

var employee Employee
employee.Name = "Eon"
employee.Rank = "staff"
employee.No = 200803
employee.Department = "Development"
employee.Vacation = 15
employee.AnnualIncome = 50000000
employee.Email = "vamos_eon@email.com"
employee.InCharge = "backend"

fmt.Println(employee)
// {Eon staff 200803 Development 15 50000000 vamos_eon@email.com backend}

구조체 변수 employee에 대하여 속성을 모두 초기화하고 출력했습니다.
주의 : 초기화하지 않으면 해당 필드에 대해서는 zero-value로 초기화됩니다.


📍 방법2. 모든 필드를 한 번에 초기화

var employee Employee
employee = Employee{ 
	"Kim", 
	"Manager", 
	210109, 
	"DEV", 
	20, 
	70000000, 
	"kim@email.com", 
	"back-end", 
}

fmt.Println(employee)
// {Kim Manager 210109 DEV 20 70000000 kim@email.com back-end}

필드 순서에 맞추어 값을 초기화할 수도 있습니다.
주의 : 중간에 초기화하지 않는 필드를 작성조차 하지 않으면 타입이 안 맞을 수 있어, 에러가 발생하기 때문에 zero-value로라도 꼭 초기화를 해야 합니다.


📍 방법3. 선택한 필드만 한 번에 초기화

var employee Employee = Employee{Name: "Eon", Email: "vamos_eon@email.com"}
fmt.Println(employee)

employee = Employee{Name: "Kim"}
fmt.Println(employee)
// {Eon  0  0 0 vamos_eon@email.com }
// {Kim  0  0 0  }

선택한 필드만 초기화할 수도 있습니다.
주의 : 이미 초기화된 필드가 있는 상태에서 위와 같은 방법으로 대입을 하면, 선택된 필드 외에는 zero-value가 대입됩니다.



📝 구조체를 포함하는 구조체

구조체를 포함하는 구조체는 조금 더 상세한 표현을 가능하게 합니다.

처음에 그린 구조와 조금 달라졌습니다.
처음에는 직원 한 명에 대해서만 정보를 넣으면 됐으니 그 한 명을 특정하고 정보를 입력했다면, 이번에는 부서 전체에 대하여 그 구성원들의 정보를 넣을 겁니다.

저렇게 많은 것들을 넣게 되면 개념을 잡을 때 복잡할 수 있으니, 일단은 아래 그림과 같이 부서를 중심으로 직원1에 대해서만 구조체를 간단히 선언해보겠습니다.


📌 구조체를 포함하는 구조체의 선언

type 구조체명1 struct {
	필드명 타입
	...
	필드명 구조체명2
	...
}

type 구조체명2 struct {
	필드명 타입
	...
	필드명 타입
	...
}

구조체 안에 타입을 구조체로 둠으로써, 구조체 안의 구조체를 선언했습니다.

위의 사용법을 바탕으로 부서에 대한 구조체를 작성해보겠습니다.

type Department struct {
	DepName		string
	Location	string
	Project		string
	Employee1	Employee
}

type Employee struct {
	Name		string
	Rank		string
	No		uint32
	Vacation	int8
	AnnualIncome	uint64
	Email		string
	InCharge	string
}

구조체를 작성했습니다.


📌 구조체를 포함하는 구조체 변수의 선언

var 구조체변수명 구조체타입명

이어서, 구조체 변수를 선언하겠습니다.

var department Department

이제 구조체 변수 department는 구조체 Department가 가지는 필드를 모두 사용할 수 있습니다.


📌 구조체를 포함하는 구조체 변수의 초기화

위에서 선언한 구조체 변수 department에 값을 초기화해서 사용해보겠습니다.
구조체 변수를 초기화하기 위한 방법은 위에서 소개한 구조체 변수의 초기화와 동일합니다.
다만, 구조체 내부의 구조체에 접근하려면 점(.) 을 두 번 사용해서 접근합니다.

📍 방법 1. 각 필드를 따로 초기화

var department Department

department.DepName = "Development"
department.Location = "8F"
department.Project = "Hello World!!"

department.Employee1.Name = "Eon"
department.Employee1.Rank = "staff"
department.Employee1.No = 200803
department.Employee1.Vacation = 15
department.Employee1.AnnualIncome = 50000000
department.Employee1.Email = "vamos_eon@email.com"
department.Employee1.InCharge = "backend"

fmt.Println(department)
// {Development 8F Hello World!! {Eon staff 200803 15 50000000 vamos_eon@email.com backend}}

구조체 변수 department 대하여 속성을 모두 초기화하고 출력했습니다.

주의 : 초기화하지 않으면 해당 필드에 대해서는 zero-value로 초기화됩니다.


📍 방법2. 모든 필드를 한 번에 초기화

var department Department = Department{
	"Development",
	"8F",
	"Hello World!!",
	Employee{
		"Eon",
		"staff",
		200803,
		15,
		50000000,
		"vamos_eon@email.com",
		"backend",
	},
}

fmt.Println(department)
// {Development 8F Hello World!! {Eon staff 200803 15 50000000 vamos_eon@email.com backend}}

필드 순서에 맞추어 값을 초기화할 수도 있습니다.
구조체 안의 구조체답게 필드 값을 초기화할 때 구조체 형태로 초기화합니다.

주의 : 중간에 초기화하지 않는 필드를 작성조차 하지 않으면 타입이 안 맞을 수 있어, 에러가 발생하기 때문에 zero-value로라도 꼭 초기화를 해야 합니다.


📍 방법3. 선택한 필드만 한 번에 초기화

var department Department = Department{
	DepName: "Development",
	Employee1: Employee{
		Name:  "Eon",
		Email: "vamos_eon@email.com",
	},
}

fmt.Println(department)
// {Development   {Eon  0 0 0 vamos_eon@email.com }}

선택한 필드만 초기화할 수도 있습니다.

var department Department = Department{
	"Development",
	"8F",
	"Hello World!!",
	Employee{
		Name:  "Eon",
		Email: "vamos_eon@email.com",
	},
}

fmt.Println(department)
// {Development 8F Hello World!! {Eon  0 0 0 vamos_eon@email.com }}

부분적으로 필드 선택 방식을 사용할 수도 있습니다.
주의 : 이미 초기화된 필드가 있는 상태에서 위와 같은 방법으로 대입을 하면, 선택된 필드 외에는 zero-value가 대입됩니다.



📝 필드명을 사용하지 않고 구조체를 포함하는 구조체

말이 굉장히 길고 복잡합니다. 하지만 다르게 표현할 수 있는 방법을 모르겠습니다.
굳이 표현한다면 '내장 구조체' 정도로 정리할 수 있겠습니다.

embedded struct field 방식이라고 합니다.
embedded struct field 방식은 구조체 필드를 선언할 때, 필드명을 작성하지 않고 타입만 선언합니다.


📌 내장 구조체의 선언

type 구조체명1 struct {
	필드명 타입
	...
	구조체명2
	...
}

type 구조체명2 struct {
	필드명 타입
	...
}

필드명 없이, 구조체 타입인 구조체명만 적어서 내장 구조체를 선언합니다.

위의 선언 방법을 바탕으로 구조체를 작성해보겠습니다.

type Department struct {
	DepName		string
	Location	string
	Project		string
	Employee
}

type Employee struct {
	Name		string
	Rank		string
	No		uint32
	Vacation	int8
	AnnualIncome	uint64
	Email		string
	InCharge	string
}

내장 구조체 선언을 했습니다.
Department구조체 내의 Employee 구조체가 내장 구조체로 선언된 것을 볼 수 있습니다.


📌 내장 구조체를 포함하는 구조체 변수의 초기화

위에서 선언한 내장 구조체를 포함하는 구조체 변수 department에 값을 초기화해서 사용해보겠습니다.
내장 구조체 변수를 초기화하기 위한 방법은 지금까지와는 다릅니다.
초기화할 때 필드명을 적어주었던 게 전부 빠지고 구조체명으로 대체되기 때문입니다.
특이한 점으로는 내장 구조체 필드에 접근할 때, 점(.) 을 하나만 사용해도 됩니다.

📍 방법 1. 각 필드를 따로 초기화

var department Department

department.DepName = "Development"
department.Location = "8F"
department.Project = "Hello World!!"
department.Name = "Eon"
department.Rank = "staff"
department.No = 200803
department.Vacation = 15
department.AnnualIncome = 50000000
department.Email = "vamos_eon@email.com"
department.InCharge = "backend"

fmt.Println(department)
// {Development 8F Hello World!! {Eon staff 200803 15 50000000 vamos_eon@email.com backend}}

구조체 변수 department에 대하여 속성을 모두 초기화하고 출력했습니다.
필드명이 없기 때문에 내장 구조체의 필드에 접근할 때는 구조체에서 바로 접근할 수 있습니다.

주의 : 초기화하지 않으면 해당 필드에 대해서는 zero-value로 초기화됩니다.


📍 방법2. 모든 필드를 한 번에 초기화

var department Department = Department{
	"Development",
	"8F",
	"Hello World!!",
	Employee{
		"Eon",
		"staff",
		200803,
		15,
		50000000,
		"vamos_eon@email.com",
		"backend",
	},
}

fmt.Println(department)
// {Development 8F Hello World!! {Eon staff 200803 15 50000000 vamos_eon@email.com backend}}

이 방법은 위에 소개한 구조체를 포함하는 구조체 변수의 초기화 방법2 와 동일합니다.

주의 : 중간에 초기화하지 않는 필드를 작성조차 하지 않으면 타입이 안 맞을 수 있어, 에러가 발생하기 때문에 zero-value로라도 꼭 초기화를 해야 합니다.


📍 방법3. 선택한 필드만 한 번에 초기화

var department Department = Department{
	DepName: "Development",
	Employee: Employee{
		Name:  "Eon",
		Email: "vamos_eon@email.com",
	},
}

fmt.Println(department)
// {Development   {Eon  0 0 0 vamos_eon@email.com }}

선택한 필드만 초기화할 수도 있습니다.
단, 보시는 바와 같이 구조체명을, 필드명 대신 작성해야 합니다.

var department Department = Department{
	"Development",
	"8F",
	"Hello World!!",
	Employee{
		Name:  "Eon",
		Email: "vamos_eon@email.com",
	},
}

fmt.Println(department)
// {Development 8F Hello World!! {Eon  0 0 0 vamos_eon@email.com }}

부분적으로 필드 선택 방식을 사용할 수도 있습니다.
주의 : 이미 초기화된 필드가 있는 상태에서 위와 같은 방법으로 대입을 하면, 선택된 필드 외에는 zero-value가 대입됩니다.


📌 같은 필드명을 가지고 있는 내장 구조체의 선언

type 구조체명1 struct {
	필드명1 타입
	...
	구조체명2
	...
}

type 구조체명2 struct {
	필드명1 타입
	...
}

위와 같이 서로 다른 구조체에 필드명이 중복되어 있고, 그 두 구조체가 내장 구조체의 관계를 가지고 있습니다.
이 때, 각 구조체의 필드명1에 값을 서로 다르게 부여하고 두 필드명1에 모두 접근할 수 있습니다.

위의 선언 방법을 바탕으로 구조체를 작성해보겠습니다.

type Department struct {
	Name		string
	Location	string
	Project		string
	Employee
}

type Employee struct {
	Name		string
	Rank		string
	No		uint32
	Vacation	int8
	AnnualIncome	uint64
	Email		string
	InCharge	string
}

두 구조체의 Name이라는 필드명이 중복되어 있는 것을 볼 수 있습니다.


📌 같은 필드명을 가지고 있는 내장 구조체 변수의 초기화

같은 필드명을 가지고 있는 내장 구조체 변수의 초기화 방법은 또 조금 다릅니다.
초기화할 때 필드명을 적어주었던 게 전부 빠지고 구조체명으로 대체되기 때문입니다.
특이한 점으로는 내장 구조체 필드에 접근할 때, 점(.) 을 하나만 사용해도 됩니다.

📍 방법 1. 각 필드를 따로 초기화

var department Department

department.Name = "Development"
department.Location = "8F"
department.Project = "Hello World!!"
department.Employee.Name = "Eon"
department.Rank = "staff"
department.No = 200803
department.Vacation = 15
department.AnnualIncome = 50000000
department.Email = "vamos_eon@email.com"
department.InCharge = "backend"

fmt.Println(department)
// {Development 8F Hello World!! {Eon staff 200803 15 50000000 vamos_eon@email.com backend}}

구조체 변수 department에 대하여 속성을 모두 초기화하고 출력했습니다.
필드명이 없기 때문에 내장 구조체의 필드에 접근할 때는 구조체에서 바로 접근할 수 있습니다.

같은 필드명의 내장 구조체 내의 필드에 접근하려면 구조체명을 사용해서 접근할 수 있습니다.
department.Employee.Name = "Eon"
같은 필드명이 아니더라도 구조체명을 사용해서 접근할 수도 있습니다.
주의 : 초기화하지 않으면 해당 필드에 대해서는 zero-value로 초기화됩니다.


📍 방법 2. 모든 필드를 한 번에 초기화

위에서 소개한 방식과 같습니다.

📍 방법3. 선택한 필드만 한 번에 초기화

위에서 소개한 방식과 같습니다.



📝 구조체 배열

구조체도 배열의 형태로 저장할 수 있습니다.
위에서 보여드린 구조체에 대한 그림 중, 아래의 그림을 예시로 사용하겠습니다.

📌 구조체 배열의 선언

type 구조체명1 struct {
	필드명 타입
	...
	필드명 [배열크기]구조체명2
	...
}

type 구조체명2 struct {
	필드명 타입
	...
}

위와 같은 방법으로 구조체 배열을 포함하는 구조체를 선언합니다.

위의 선언 방법을 바탕으로 구조체를 작성해보겠습니다.

type Department struct {
	DepName		string
	Location	string
	Project		string
	EmployeeInfo	[1]Employee
}

type Employee struct {
	Name		string
	Rank		string
	No		uint32
	LeaveInfo	Leave
	AnnualIncome	uint64
	Email		string
	InCharge	[1]Tasks
}

type Leave struct {
	AnnualLeave		uint8
	BereavementLeave	uint8
	LongServiceLeave	uint8
 	SickLeave		uint8
}

type Tasks struct {
	Name		string
	Duration	uint8
	Progress	uint8
}

📌 구조체 배열 변수의 초기화

var OurComapny [1]Department = [1]Department{}

OurComapny[0].DepName = "Development"
OurComapny[0].Location = "8F"
OurComapny[0].Project = "Hello World!!"
OurComapny[0].EmployeeInfo[0].Name = "Eon"
OurComapny[0].EmployeeInfo[0].Rank = "staff"
OurComapny[0].EmployeeInfo[0].Email = "vamos_eon@email.com"
OurComapny[0].EmployeeInfo[0].AnnualIncome = 50000000
OurComapny[0].EmployeeInfo[0].No = 200803
OurComapny[0].EmployeeInfo[0].LeaveInfo.AnnualLeave = 15
OurComapny[0].EmployeeInfo[0].LeaveInfo.BereavementLeave = 5
OurComapny[0].EmployeeInfo[0].LeaveInfo.LongServiceLeave = 0
OurComapny[0].EmployeeInfo[0].LeaveInfo.SickLeave = 30
OurComapny[0].EmployeeInfo[0].InCharge[0].Name = "Golang installation"
OurComapny[0].EmployeeInfo[0].InCharge[0].Duration = "~2022-01-16"
OurComapny[0].EmployeeInfo[0].InCharge[0].Progress = "80%"

위와 같이 초기화해서 접근할 수 있습니다.

slice로 선언하면 훨씬 유동적이고 쉽게 초기화 및 접근이 가능합니다.
slice에 대한 내용은 나중에 다루겠습니다.



📝 메모리 할당

📌 구조체의 메모리 할당

구조체의 메모리 할당에 가장 큰 특징은 메모리 패딩입니다.
빈 공간을 추가로 할당하는 것인데, 왜 그런지 알아보겠습니다.

📍 레지스터에 대한 이해

운영체제에 대한 이야기를 조금이라도 들어봤다면 32비트 운영체제64비트 운영체제가 있다는 것은 알 수 있습니다.

요즘 나오는 운영체제는 대부분이 64비트 운영체제입니다.
더 많은 양을 처리할 수 있기 때문에 뭔가 특별한 제약이 있는 경우가 아니라면 32비트를 쓸 이유가 없습니다.

이 32비트와 64비트가 의미하는 것은 바로 '한 번에 처리할 수 있는 데이터의 크기' 라고 보시면 됩니다.
정확히는 주소의 길이입니다.
32비트 운영체제는 주소의 단위가 4바이트입니다. 따라서 한 번에 처리할 수 있는 데이터의 크기는 4바이트입니다.
64비트 운영체제는 주소의 단위가 8바이트입니다. 따라서 한 번에 처리할 수 있는 데이터의 크기는 8바이트입니다.

우리가 코딩을 하고 컴파일한 후에 실행하면, 선언한 변수들에 대한 메모리가 할당이 됩니다.
그 변수가 사용될 때, 데이터가 RAM에 먼저 올라가게 되고, CPU가 연산을 하기에 앞서, Register에 해당 데이터를 가지고 온 다음에 CPU가 Register에 올라온 데이터를 가지고 연산을 합니다.

32비트 운영체제에서 8바이트짜리 데이터는 두 번 읽어서 사용합니다.
DRAM과 캐시를 이용하는데, 이것은 운영체제에 관련한 공부를 따로 요구합니다.
아무튼 32비트 운영체제에서도 8바이트짜리 데이터를 읽고 사용하는 데에는 문제가 없습니다.

대개 64비트 운영체제를 사용하기 때문에, 운영체제는 메모리 할당을 8바이트의 배수 단위로 하려고 할 것입니다.
이것이 '메모리 정렬' 입니다.

📍 메모리 정렬과 메모리 패딩

64비트 운영체제는 메모리를 8바이트 단위로 할당한다고 했습니다.
한 번에 처리할 수 있는 데이터의 최대 크기가 8바이트이기 때문입니다.
bool 변수를 하나 선언하면 1바이트입니다.
int16 변수를 하나 선언하면 2바이트입니다.
이 두 변수를 선언하면 총 3바이트입니다.

그런데, 위의 두 변수를 하나의 구조체에서 사용하면 메모리 정렬이 일어납니다.
변수를 따로 선언하는 것은, 메모리의 어디에든 따로 할당되어 있어도 상관없습니다.
하지만 구조체는 구조체 자체가 하나의 변수처럼 되어 있는 구조이므로 연속된 메모리에 할당됩니다.
구조체 안에 boolint16을 선언하겠습니다.

type structure1 struct {
	vBool	bool
	vInt16	int16
}

...
fmt.Println(unsafe.Sizeof(structure1{})
// 4

structure1의 크기는 4바이트입니다.
메모리 정렬로 인해 생긴 메모리 패딩 때문입니다.

메모리 정렬은 CPU가 메모리에서 데이터를 가지고 올 때 효율적으로 동작하기 위해 수행합니다.
메모리 정렬은 구조체에 선언된 변수의 순서대로 진행되며, 구조체 내의 변수 중 가장 큰 사이즈가 기준이 됩니다.
위의 경우, vInt16structure1 내의 가장 사이즈가 큰 변수이므로 int16의 사이즈인 2바이트가 데이터를 불러올 때의 단위가 됩니다.
그래서 bool 타입의 vBool을 메모리에 할당할 때, 2바이트로 할당하게 되는 것입니다.
그렇게 bool 타입의 실제 사이즈인 1바이트가 아닌 해당 변수에 2바이트가 할당되어 사용하지 않는 1바이트가 생겨나게 됩니다.
이것이 메모리 패딩입니다.

예시를 한 가지 더 보겠습니다.
아래와 같이 구조체를 선언해보겠습니다.

type structure2 struct {
	v1Int32	int32
	vInt16	int16
	v2Int32	int32
	vBool	bool
}


보시는 바와 같이 구조체 내의 가장 큰 변수 사이즈인 int32에 맞추어, 4바이트를 기준으로 메모리 할당이 된 것을 볼 수 있습니다.
4바이트 간격으로 int16은 2바이트를 차지하고, 전체 4바이트 공간 중 나머지 2바이트는 사용하지 않는 메모리 패딩으로 남습니다.


📍 구조체를 만드는 가장 좋은 방법은?

구조체를 만들 때 필드의 사이즈를 내림차순으로 만드는 것이 가장 좋습니다.
자료형들의 사이즈를 보면 모두 2의 거듭제곱으로 표현된다는 것을 알 수 있습니다.
각각 1, 2, 4, 8 바이트입니다.
내림차순으로 만들게 되면 메모리 패딩을 줄이고 구조체의 크기를 줄일 수 있습니다.

type structure2 struct {
	v1Int32	int32
	v2Int32	int32
	vInt16	int16
	vBool	bool
}



📌 구조체 사이즈 확인

위에서 잔뜩 메모리 정렬과 메모리 패딩에 대해서 이야기했는데 정작 메모리가 실제로 어떻게 잡히는지 사이즈 측정을 하지 않았습니다.
여기서는 사이즈를 구해보도록 하겠습니다.

unsafe 패키지 를 이용하면 됩니다.
unsafe 패키지는 안전하지 않은 유형들을 포함하는 패키지입니다.
사용할 때는 조심해야 하며 Go 1 호환성 가이드라인으로 보호받지 않습니다.

📍 Sizeof() : func Sizeof(x ArbitraryType) uintptr

ArbitraryType은 실제 unsafe패키지에 포함된 타입이 아닙니다.
문서화를 위한 타입일 뿐입니다.

Sizeof() 함수는 그 타입의 크기를 바이트 단위로 반환합니다.
단, 참조되는 메모리의 크기를 그대로 반환하는 것은 아닙니다.
slice와 같은 경우, 메모리의 크기가 아니라 slice 설명자의 크기를 반환한다고 되어 있습니다.

📍 unsafe : 변수와 구조체 사이즈 구하기

여기서, 변수의 크기와 구조체의 크기를 구해보겠습니다.

package main

import (
	"fmt"
	"unsafe"
)

func main() {
	fmt.Printf("Size of %T : %d\n", int64(0), unsafe.Sizeof(int64(0)))
	fmt.Printf("Size of %T : %d\n", int32(0), unsafe.Sizeof(int32(0)))
	fmt.Printf("Size of %T : %d\n", int16(0), unsafe.Sizeof(int16(0)))
	fmt.Printf("Size of %T : %d\n", int8(0), unsafe.Sizeof(int8(0)))
	fmt.Printf("Size of %T : %d\n", bool(false), unsafe.Sizeof(bool(false)))
}
/* The result of the ouput
Size of int64 : 8
Size of int32 : 4
Size of int16 : 2
Size of int8 : 1
Size of bool : 1
*/

golang의 자료형 사이즈대로 잘 나오는 것을 확인할 수 있습니다.


구조체의 크기도 구해보겠습니다.

package main

import (
	"fmt"
	"unsafe"
)

type structure1 struct {
	vBool  bool
	vInt16 int16
}

func main() {
	var s structure1
	fmt.Printf("Size of %T struct: %d bytes\n", s, unsafe.Sizeof(s))
}
// Size of main.structure1 struct: 4 bytes

위에서 설명드린 바와 같이, bool 타입에 1바이트의 메모리 패딩이 붙어, 총 4바이트가 된 것을 확인할 수 있습니다.


📌 string의 메모리 할당

string의 크기를 구해보겠습니다.

📍 string의 사이즈 구하기

stringSizeof() 함수로 크기를 구해보면 string 변수의 값이 말도 안 되게 길어도 항상 16바이트를 반환합니다.

fmt.Printf("Size of %T : %d\n", string("abcdefghijklmnopqrstuvwxyz"), unsafe.Sizeof(string("abcdefghijklmnopqrstuvwxyz")))
// Size of string : 16

unsafe.Sizeof(string(""))string의 설명자의 크기를 반환하기 때문입니다.
stringStringHeader, 바이트 데이터로 구성되어 있습니다.

type StringHeader struct {
	Data uintptr
	Len  int
}

StringHeader는 위와 같습니다.
Data는 실제 데이터가 있는 위치, Len은 문자열 길이를 저장합니다.
따라서, StringHeader의 크기는 uintptr(8bytes) + int(8bytes)로, 16바이트가 됩니다.

string 타입의 데이터가 실제로 메모리에 할당되는 크기는 아래와 같습니다.

var str string = "Hello"
stringSize := len(str) + int(unsafe.Sizeof(str))
fmt.Println(stringSize)
// 21

string 타입의 실제 메모리 = 문자열 길이 + StringHeader의 크기

📌 string을 포함한 구조체의 크기

unsafe.Sizeof(string(""))StringHeader의 크기를 반환한다고 했습니다.
구조체에서는 필드 중 가장 사이즈가 큰 값을 기준으로 메모리를 할당한다고 했습니다.
string이 끼어 있는 경우, 메모리 할당이 어떻게 이루어지는지 보겠습니다.

type strInclude struct {
	vStr	string
	vInt64	int64
}
...
fmt.Println(unsafe.Sizeof(strInclude{}))
// 24

이렇게 할당됩니다.
이유는 간단합니다.
unsafe.Sizeof(strInclude{})StringHeader의 사이즈를 반환하고, StringHeaderuintptr, int로 이루어져 있으며 이 두 자료형 모두 8바이트입니다.

따라서 아래와 같이 8바이트 단위로 메모리 정렬 및 할당이 이루어집니다.

당연하게도, strInclude 구조체 내에 int64 자료형의 필드가 int32가 되더라도 strInclude의 크기는 24로 나오게 됩니다.


이번 포스팅은 구조체에 대한 내용이었습니다.
감사합니다.👍

profile
주니어 개발자

1개의 댓글

comment-user-thumbnail
2023년 1월 26일

좋은 자료 감사합니다!

답글 달기