"원리" 중심으로 공부하기!
스택 구조에서 rsp(Stack Pointer)가 어떻게 움직이는지는 함수 호출에 따른 push, call, pop, ret 명령의 동작으로 설명할 수 있습니다. x86-64 아키텍처에서 스택은 “낮은 주소 방향으로 자라며” rsp는 항상 스택의 최상단(top)을 가리킵니다.
+-------+
| rsp | # 현재 위치 (스택의 최상단)
+-------+
rsp는 현재 스택 최상단을 가리킴.main() 실행 중이기 때문에, foo()는 호출되지 않음.call foo 실행 시call 명령은 다음 두 일을 자동으로 수행합니다:
1. 리턴 주소(return address)를 스택에 push
2. foo 함수 주소로 점프
따라서 스택은 이렇게 변합니다.
+-------+
| 1175 | # 리턴 주소 (main이 foo 호출 직후 돌아올 주소)
+-------+
| rsp | # rsp가 여기로 이동 (8바이트 낮아짐)
+-------+
즉, rsp는 8바이트만큼 감소합니다 (64비트는 8바이트 단위).
대부분의 컴파일된 C 코드에서 함수 시작 시 다음 두 명령이 실행됩니다:
push rbp
mov rbp, rsp
push rbp는 이전 함수의 베이스포인터를 스택에 저장합니다 → rsp 8바이트 감소 mov rbp, rsp는 현재 스택의 최상단을 새 함수의 기준점으로 설정합니다 결과:
+-------+
| 1160 | # 이전 RBP 저장
+-------+
| 1175 | # 리턴 주소
+-------+
| rsp | # rsp가 여기로 이동 (foo의 스택 프레임 시작)
+-------+
sub rsp, N (예: sub rsp, 16) 형태로 지역 변수 공간 확보:
+-------+
| 지역변수 | ...
+-------+
| 1160 | # 이전 RBP
+-------+
| 1175 | # 리턴 주소
+-------+
| rsp | # rsp가 더 낮은 주소로 이동
+-------+
즉, 스택이 커질수록 rsp는 작아집니다.
일반적인 함수 종료 시 다음 명령들이 실행됩니다:
leave ; == mov rsp, rbp + pop rbp
ret
mov rsp, rbp: 스택을 원래 프레임 크기만큼 되돌림 pop rbp: 이전 함수의 RBP 복원 (rsp 8바이트 증가) ret: 스택에서 리턴 주소를 꺼내 점프 (rsp 8바이트 증가)결과적으로 rsp는 원래 main의 위치로 복귀합니다:
+-------+
| rsp | # 다시 main의 스택 위치로 복귀
+-------+
| 단계 | 동작 | rsp 변화량 | 설명 |
|---|---|---|---|
| main 실행 중 | 초기 상태 | 0 | 스택 프레임 설정 완료 |
| call foo | -8 bytes | 리턴 주소 push | |
| push rbp | -8 bytes | 이전 베이스포인터 저장 | |
| sub rsp, N | -N bytes | 지역변수 공간 확보 | |
| mov rsp, rbp | +N bytes | 공간 반환 | |
| pop rbp | +8 bytes | 이전 RBP 복원 | |
| ret | +8 bytes | 리턴 주소 pop 후 main 복귀 |
즉, 스택은 항상 아래로 자라며, rsp는 push·call로 감소, pop·ret으로 증가합니다. 이런 변화로 함수 호출 계층이 안전하게 정리·복원됩니다.
push rbp, mov rbp, rspmov rsp, rbp, pop rbpsub %rsq, 16+로 접근, 모든 지역변수는 -로 접근%rbp+8,%ecx(%rbp-4),%edxbp 백업
bp 상승
argument 넘기기: 레지스터 이용(개수 적을 때)
변수 해지 & rbp 되돌리기 → leave
함수를 갔다가 돌아오면 무조건 실행: 프롤로그와 에필로그
push
mov
sub
nop
leave
ret
argument는 모두 stack에 쌓아서 씀
이름으로 바이트 제어 ax, eax, rax
https://cafe.naver.com/f-e/cafes/19799898/menus/317
#include <stdio.h>
int main()
{
// "123" → 123
print("%c\n", '1'); // 1
print("%d\n", '1'); // 49
print("%d\n", 'a'); // 97
print("%d\n", 'A'); // 65
print("%d\n", '\n'); // 10
print("%d\n", '\r'); // carriage return, 13
print("%d\n", '\0'); // 0
print("%d\n", ' '); // 32
print("%d\n", '1'-'0'); // 1
return 0;
}
↓
↓
↓
모듈화
↓
#include <stdio.h>
int toInt(const char *str);
int main() {
char str[] = "123";
int num = toInt(str);
printf("%d\n", num); // 출력: 123
return 0;
}
int toInt(const char *str) {
int result = 0;
for (int i = 0; str[i] != '\0'; i++) {
result = result * 10 + (str[i] - '0');
}
return result;
}
-fPIC#include <stdio.h>
int main()
{
int item[4] = {0,};
}
| 0 | 1 | 2 | 3 |
|---|---|---|---|
| 0 | 0 | 0 | 0 |
#include <stdio.h>
int main()
{
int item[4] = {0,};
item[0] = 1;
for (i=0; i<4; i++)
if(item[0]==1)
printf("%d\n", i);
}
| 0 | 1 | 2 | 3 |
|---|---|---|---|
| 1 | 0 | 0 | 0 |
#include <stdio.h>
int main()
{
int item[4] = {0,};
item[0] = 1;
item[3] = 1;
for (i=0; i<4; i++)
if(item[0]==1)
printf("%d\n", i);
item[0] = 0;
for (i=0; i<4; i++)
if(item[0]==1)
printf("%d\n", i);
}
| 0 | 1 | 2 | 3 |
|---|---|---|---|
| 1 | 0 | 0 | 1 |
| 0 | 0 | 0 | 1 |
#include <stdio.h>
int main()
{
int item = 0;
int i;
item = item | 1;
item = item | 8;
for (i=0; i<4; i++)
if(item&(1<<i))
printf("%d\n", i);
}
| 0 | 1 | 2 | 3 |
|---|---|---|---|
| 1 | 0 | 0 | 1 |
#include <stdio.h>
int main()
{
int item = 0;
int i;
item = item | 1;
item = item | 8;
for (i=0; i<4; i++)
if(item&(1<<i))
printf("%d\n", i);
item = item & ~8;
}
index=100;
item[index/32] |= 1<<(index%32);
#include <stdio.h>
int main()
{
int item[32] = {0,};
int i;
item[700/32] |= 1<<(700%32);
item[800/32] |= 1<<(800%32);
for (i=0; i<1024; i++)
if(item&(1<<i))
printf("%d\n", i);
item[800/32] &= ~(1<<(800%32));
}
자주 쓰고 간단한 함수는 메크로로 처리
#include <stdio.h>
# define BIT_SET(x, index) (x[index/32] |= 1<<(index%32))
# define BIT_ISSET(x, index) (x[index/32] & (1<<(1index%32)))
# define BIT_CLR(x, index) (x[index/32] &= ~(1<<(1index%32)))
//-----
int main()
{
int item[32] = {0,};
int i;
BIT_SET(item, 700);
BIT_SET(item, 800);
for (i=0; i<1024; i++)
if(BIT_ISSET(item,i))
printf("%d\n", i);
BIT_CLR(item,800);
}
#include <stdio.h>
# define BIT_SET(x, index) (x[index/32] |= 1<<(index%32))
# define BIT_ISSET(x, index) (x[index/32] & (1<<(1index%32)))
# define BIT_CLR(x, index) (x[index/32] &= ~(1<<(1index%32)))
typedef int bit_set[32];
//-----
int main()
{
bit_set item = {0,};
int i;
BIT_SET(item, 700);
BIT_SET(item, 800);
for (i=0; i<1024; i++)
if(BIT_ISSET(item,i))
printf("%d\n", i);
BIT_CLR(item,800);
}
#include <stdio.h>
# define BIT_SET(x, index) (x[index/32] |= 1<<(index%32))
# define BIT_ISSET(x, index) (x[index/32] & (1<<(1index%32)))
# define BIT_CLR(x, index) (x[index/32] &= ~(1<<(1index%32)))
# define BIT_ZERO do{int i;\
for(i=0; i<32; i++)\
item[0]=0;}while(0)
typedef int bit_set[32];
//-----
int main()
{
bit_set item = {0,};
int i;
BIT_SET(item, 700);
BIT_SET(item, 800);
for (i=0; i<1024; i++)
if(BIT_ISSET(item,i))
printf("%d\n", i);
BIT_CLR(item,800);
}
전처리기는 파이썬과 같은 인터프리터 → 한 줄만 읽어옴
유저가 입력한 ;이 오류가 되지 않도록 만들어주면 됨 → do - while(0)
| 구조 요소 | 설명 |
|---|---|
| Super Block | 파일시스템 전체 정보 저장 |
| Group Descriptor Table (GDT) | 각 블록그룹의 위치 및 크기 정보 |
| Block Bitmap | 해당 그룹 내 블록 사용 여부 (확장 비트맵) |
| Inode Bitmap | 해당 그룹의 inode 사용 상태 표시 |
| Inode Table | 파일/디렉토리의 메타데이터 저장 |
| Data Blocks | 실제 파일 데이터 저장 공간 |
+---------------------------------------+
| Superblock (파일시스템 전체 정보) |
| Group Descriptor Table (GDT) |
+---------------------------------------+
| Block Group #0 | Block Group #1 | ... |
| ├─ Block Bitmap (확장 비트맵) |
| ├─ Inode Bitmap |
| ├─ Inode Table |
| └─ Data Blocks |
+---------------------------------------+
| 블록 번호 | 상태 | 의미 |
|---|---|---|
| 0 | 1 | 사용 중 |
| 1 | 0 | 비어 있음 |
| 2 | 0 | 비어 있음 |
| 3 | 1 | 사용 중 |
| 4 | 0 | 비어 있음 |
| 5 | 1 | 사용 중 |
| 6 | 1 | 사용 중 |
| 7 | 0 | 비어 있음 |
| 특징 | 설명 |
|---|---|
| 계층 구조 | 각 블록 그룹이 자체 비트맵을 가지므로, 전체 파일시스템 비트맵을 분산 관리 |
| 확장성(Extendibility) | 대규모 파일시스템에서도 비트맵 구조를 그룹 단위로 확장하여 사용 |
| 빠른 탐색 | 특정 그룹 내 비어있는 블록을 빠르게 찾을 수 있음 |
| 일관성 보장 | 각 그룹의 비트맵이 슈퍼블록과 동기화되어 저장됨 |
| 복구 효율성 향상 | 손상 시 그룹 단위 복구 가능 (ext4의 저널링 활용) |
블록 사용 시 해당 비트를 "1"로 갱신.
파일 삭제 시에는 "1 → 0"으로 바꿔 해당 블록을 재사용 가능하게 만든다.
Disk 전체
┌──────────────────────────────────────────┐
│ 그룹 0 │
│ ├─ Block Bitmap → 1 0 0 1 0 1 0 0 │
│ ├─ Inode Bitmap → 1 1 0 0 1 0 0 0 │
│ └─ Data blocks ... │
│ │
│ 그룹 1 │
│ ├─ Block Bitmap → 0 1 1 1 0 0 0 0 │
│ ├─ Inode Bitmap → 1 0 0 0 0 0 0 1 │
│ └─ Data blocks ... │
└──────────────────────────────────────────┘
결론적으로, 확장 비트맵(Extended Bitmap) 은 단순한 비트맵 구조를 “그룹 단위로 확장한 형태”로,
리눅스 EXT 파일시스템에서는 데이터 블록과 inode의 사용 상태를 효율적으로 추적하기 위해 사용된다
이 덕분에 대용량 디스크에서도 빠르고 안정적인 파일 관리가 가능해진다
항목 | 내용
--------+---------------------------------------------
개념 | 디스크 블록/아이노드의 사용 상태를 관리하는 확장된 비트맵 구조
핵심 요소 | Block Bitmap, Inode Bitmap (블록 그룹 단위 관리)
특징 | 효율적인 확장성, 빠른 빈 블록 탐색, 분산된 구조
예시 시스템 | ext2,ext3,ext4파일 시스템
결과 | 대형 스토리지에서도 빠른 블록 할당/해제가 가능하도록 설계된 비트맵 확장 구조
struct_node
{
int data;
struct _node *next;
};
typedf struct_node
{
int data;
struct _node *next;
} NODE;
Node temp;
typedf struct_node
{
int data;
struct _node *next;
} NODE;
void insert_data(int data)
{
NODE *temp;
tenp=(NODE*)mallock(sizeof(NODE));
temp->data=data;
temp->next=head;
head=temp;
}
# if 1
#include <stdio.h>
#include <stdlib.h>
// ----------
void display()
{
NODE *temp;
printf("head");
}
int main()
{
int i;
for(i=0; i<7; i++)
dinsert_data(i+1);
display();
return 0;
}
[]로 묶기void reverse(NODE *head)
{
NODE *prev = head;
NODE *curr = prev->next;
NODE *next;
while(curr!=head)
{
next=curr->next;
curr->next=prev;
prev=curr;
curr=next;
}
cirr->next=prev;
}
Reverse a circular linked list
Reverse a doubly circular linked list
리스트에 데이터를 넣지 말고 데이터에 리스트를 넣기!
struct list_head {
19 struct list_head *next, *prev;
20};
21
22#define LIST_HEAD_INIT(name) { &(name), &(name) } // 이중 환형 구조
23
24#define LIST_HEAD(name) \
25 struct list_head name = LIST_HEAD_INIT(name)
26
27#define INIT_LIST_HEAD(ptr) do { \
28 (ptr)->next = (ptr); (ptr)->prev = (ptr); \
29} while (0)
30
/**
182 * list_entry - get the struct for this entry
183 * @ptr: the &struct list_head pointer.
184 * @type: the type of the struct this is embedded in.
185 * @member: the name of the list_struct within the struct.
186 */
187#define list_entry(ptr, type, member) \
188 ((type *)((char *)(ptr)-(unsigned long)(&((type *)0)->member)))
오픙소스 보면서 공부하기
리눅스 → C & 안드로이드 → C++
lib → 하드웨어에 의존적이지 않아서 분석하기 좋음
rbtree.c → 딕셔너리 구현 & 순회
32비트 머신으로 설명
int main()
{
int a[2];
int *p = a; // 주소 변수 p에 a 담기
return 0;
}
int main()
{
int a[2];
int *p = a; // 주소 변수 p에 a 담기
printf("sizeof(a)=%u\n", sizeof(a)); // 8
printf("sizeof(p)=%u\n", sizeof(p)); // 4
return 0;
}
포인터로 가면 무조건 타입이 같아야 함
symbol을 지운 게 타입임
값은 좀 너그러움
int[2] 와 int*
int a[2][2] = {1,2,3,4};
int *p=a;
p[1][1]=10; // compile error
↓
| 주소 예시 | ||
|---|---|---|
| 1 | a[0][0] | 1000 |
| 2 | a[0][1] | 1004 |
| 3 | a[1][0] | 1008 |
| 4 | a[1][1] | 1016 |
위와 같이 작동하지 않음을 증명하기
(*(p+1))[1]
*(*(p+1)+1)
int a[2][2] = {1,2,3,4};
int **p=a;
p[1][1]=10; // runtime error
더 큰 에러...!
int a[2][2] = {1,2,3,4};
int (*p)[2]=a;
p[1][1]=10;
↓
(*(p+1))[1]
*(*(p+1)+1)
*(*(1000+1)+1)
*(*1008+1) // 여기 1008이랑
*(1008+1) // 여기 1008은 다름
*(1016) // 4
int a[2][2][2] = {1,2,3,4,5,6,7,8};
int (*p)[2][2]=a;
p[1][1][1]=10;
직접 보이는 scope에서는 이렇게 쓸 필요 없음
(a[1][1]로 직접 하면 되니까)
모듈화 할 때 필요! → call by address
POINT
모든 타입을 인자로 넘길 수 있어야 함
모든 타입을 리턴할 수 있어야 함
int *p; int **p: int를 가리키는 포인터를 가리키는 포인터int a[2];: int 2개로 된 배열 → 2차원 배열int a[2][2];int *a[2]; ★★★int *a[2]; ★★★void foo() ★★★: void를 리턴하는 인자가 없는 함수void foo(int) ★★★: void를 리턴하는 int 인자 하나를 가지는 함수int foo(int) ★★★: int를 리턴하는 int 인자 하나를 가지는 함수void foo(*p) ★★★int (*foo())[2]
// function(int) returning
// pointer 2
// array of 2
// int
함수의 이름은 함수의 번지!
int(*aaa(void))[2] // (1)
{
static int a[2][2];
// 2행 2열 배열을 선언(함수 종료 후에도 값 유지)
return a;
// a의 첫 행(= 전체 배열의 시작 주소)을 반환
}
int(*(*bbb(void))(void))[2] // (2)
{
return aaa; // 함수 포인터 반환
}
int main()
{
int (*(*(*p[2][2])(void))(void))[2] = {{bbb,bbb},[bbb,bbb}}; // (3)
int (*(*(*(*q)[2])(void))(void))[2] = p;
q[1][1]()()[1][1]=10;
return 0;
}
(1) int 배열에 대한 포인터를 반환하는 함수 aaa()
→ aaa() 호출 시 a의 첫 번째 행(=int 배열)의 주소 반환
(2) 함수를 반환하는 함수
→ bbb()를 호출하면 aaa 함수의 주소를 반환
→ aaa 함수는 인자 없고 int 배열 포인터를 반환함
(*(*(*(*q)[2])(void))(void))[2]typedef int FP1[2];
typedef FP1* FP2;
typedef FP2 FP3(void);
typedef FP3* FP4;
typedef FP4 FP5(void);
typedef FP5* FP6;
typedef FP6 FP7[2];
typedef FP7* FP8;
// ---
FP2 aaa(void) // (1)
{
static int a[2][2];
// 2행 2열 배열을 선언(함수 종료 후에도 값 유지)
return a;
// a의 첫 행(= 전체 배열의 시작 주소)을 반환
}
FP4 bbb(boid) // (2)
{
return aaa; // 함수 포인터 반환
}
int main()
{
FP6 p[2][2] = {{bbb,bbb},[bbb,bbb}}; // (3)
FP8 q = p;
q[1][1]()()[1][1]=10;
return 0;
}
typedef int (*FP1)[2];
typedef FP1* (*FP2)(void);
typedef FP2 (*FP3)(void);
typedef FP3 (*FP4)[2];
// ---
FP1 aaa(void) // (1)
{
static int a[2][2];
// 2행 2열 배열을 선언(함수 종료 후에도 값 유지)
return a;
// a의 첫 행(= 전체 배열의 시작 주소)을 반환
}
FP2 bbb(boid) // (2)
{
return aaa; // 함수 포인터 반환
}
int main()
{
FP3 p[2][2] = {{bbb,bbb},[bbb,bbb}}; // (3)
FP4 q = p;
q[1][1]()()[1][1]=10;
return 0;
}
C++의 등장