[인공지능사관학교: 자연어분석A반] MFU 특강 (4)

Suhyeon Lee·2025년 10월 26일

"원리" 중심으로 공부하기!

스택 구조에서 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바이트 단위).


foo() 진입 후 (함수 프롤로그)

대부분의 컴파일된 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는 작아집니다.


foo() 종료(에필로그)

일반적인 함수 종료 시 다음 명령들이 실행됩니다:

leave  ; == mov rsp, rbp + pop rbp
ret
  1. mov rsp, rbp: 스택을 원래 프레임 크기만큼 되돌림
  2. pop rbp: 이전 함수의 RBP 복원 (rsp 8바이트 증가)
  3. ret: 스택에서 리턴 주소를 꺼내 점프 (rsp 8바이트 증가)

결과적으로 rsp는 원래 main의 위치로 복귀합니다:

+-------+
|  rsp  |  # 다시 main의 스택 위치로 복귀
+-------+

요약: rsp 이동 과정

단계동작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으로 증가합니다. 이런 변화로 함수 호출 계층이 안전하게 정리·복원됩니다.

rsp와 rbp

  • RSP와 RBP는 x86-64 어셈블리에서 스택(Stack) 을 관리하기 위한 중요한 레지스터
    • 함수 호출과 지역 변수 관리에 핵심적인 역할을 담당
      • RSP는 스택의 꼭대기를 지속적으로 추적하는 레지스터이고, RBP는 현재 함수의 스택 프레임 기준을 고정적으로 가리키는 포인터(RSP는 “움직이는 포인터”, RBP는 “기준점 포인터”로 이해하기)
  • RSP (Stack Pointer Register)
    • RSP는 “Stack Pointer Register” 로, 스택의 맨 위(top)를 가리킴
      • 스택은 함수 호출 시 지역 변수, 매개변수, 복귀 주소 등을 저장하는 메모리 영역이며, 후입선출(LIFO, Last In First Out) 구조를 가짐
    • 함수 내부에서 데이터를 push 하면 RSP가 아래로 내려가며(주소 감소), pop 하면 위로 올라간다(주소 증가) → 즉, RSP는 스택의 현재 상태를 실시간으로 추적하는 레지스터임
  • RBP (Base Pointer Register)
    • “Base Pointer Register” 또는 “Frame Pointer” 로, 현재 스택 프레임의 기준(base) 을 나타냄
    • 함수가 호출되면 보통 프롤로그(prologue) 에서 다음 명령이 실행: push rbp, mov rbp, rsp
      • 이로써 RBP는 그 함수의 스택 프레임의 시작 지점을 가리키게 됨
    • 이후 지역 변수는 RBP - offset, 매개변수는 RBP + offset 형태로 접근
    • 함수가 종료될 때 에필로그(epilogue) 에서 다음이 실행: mov rsp, rbp, pop rbp
      • 이를 통해 이전 함수의 스택 상태를 복구함
  • 스택 프레임(Stack Frame)
    • 각 함수 호출마다 생성되는 스택 구조 단위
    • 내부에는 다음이 포함됨:
      • 이전 함수의 RBP (저장된 프레임 포인터, SFP)
      • 복귀 주소(Return Address)
      • 지역 변수 및 매개변수
    • 이 구조 덕분에 함수 간 호출 관계가 명확히 유지되고, 함수 종료 후 정확한 원래 위치로 복귀할 수 있음

Function CALL

  • sub %rsq, 16
    • 지역변수 개수만큼 메모리 확보
  • 모든 argument는 +로 접근, 모든 지역변수는 -로 접근
    • psudocode: %rbp+8,%ecx
    • psudocode: (%rbp-4),%edx
  • 지역변수의 범위(frame)는 rbp, rsp로 구할 수 있음

bp 백업
bp 상승
argument 넘기기: 레지스터 이용(개수 적을 때)
변수 해지 & rbp 되돌리기 → leave

함수를 갔다가 돌아오면 무조건 실행: 프롤로그와 에필로그

push
mov
sub
nop
leave
ret

argument는 모두 stack에 쌓아서 씀

argument와 지역변수의 차이


MEMORY

  • 메모리 크기 (32bit): 4GB
    • 0x00000000 - 0xffffffff
  • bit, byte, nibble
  • 4등분
    • 0000 / 0100 / 1000 / 1100 (0 / 4 / 8 / c )
  • kernel에 접근하려면 주어진 방법으로만 가능

memory map

https://blog.naver.com/watney0813/220964262053

이름으로 바이트 제어 ax, eax, rax

가상 주소에서 물리 주소로

  • 물리 주소(32비트 기준)
    • 4k (2122^{12})
    • 페이지 주소로 관리
  • 가상 주소는 인덱스일 뿐!
    • CPU 내부 MMU가

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;
}

분할 컴파일

shared library

  • 위치독립적으로 메모리에 올라감: -fPIC
  • 스텁(stub)

dynamic linking library



bitmap

  • 데이터의 상태를 0과 1의 이진 비트(bit) 으로 표현하는 구조
    • 1 → 해당 블록이 할당됨(used)
    • 0 → 해당 블록이 비어 있음(free)
  • 파일 시스템이 “어떤 블록이 비어 있는지” 빠르게 관리할 수 있음
#include <stdio.h>
int main()
{
	int item[4] = {0,};
}
0123
0000
#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);
}
0123
1000
#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);
}
0123
1001
0001

문제점

  1. 메모리 낭비가 심함: on/off만 할 건데 int는 너무 많음
#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);
}
0123
1001
#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;
}
  1. 개수 제한이 있다: int는 32비트니까
    • 확장 비트맵 적용
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));
}
  1. 가독성이 없음
    • 함수로는 오버헤드 & 느림 → 메크로 함수!

매크로 함수(Macro Function)

  • C 언어에서 #define 전처리 지시문을 이용해 정의되는 전처리기 함수 형태의 기호 치환식
  • 컴파일 전에 전처리기에 의해 단순한 텍스트 치환(text substitution) 방식으로 동작하며,
    일반 함수처럼 보이지만 실제 함수 호출이 아닌 코드 삽입(substitution) 으로 이루어짐

자주 쓰고 간단한 함수는 메크로로 처리

#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);
}
  1. 타입이 추상화되어 있지 않음
#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);
}
  1. 초기화가 추상화되어 있지 않음
#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)

초기화와 할당

확장 비트맵(Extended Bitmap)

  • 일반적으로 파일 시스템(특히 ext2/ext3/ext4)이나 데이터베이스, 혹은 이미지 파일 포맷에서 비트맵(Bitmap) 구조가 확장되어 사용되는 경우를 의미

Linux EXT 파일 시스템의 “확장 비트맵”(Extended Bitmap) 구조

  • EXT (Extended File System)
    • 1990년대 리눅스에서 설계된 계층형 파일 시스템
    • “확장된 비트맵 관리 구조”를 갖고 있음
  • 블록 그룹 구조
    • EXT 계열 파일시스템은 디스크를 여러 “블록 그룹(block group)”으로 나누고, 각 그룹에 중요한 관리 비트맵을 유지
구조 요소설명
Super Block파일시스템 전체 정보 저장
Group Descriptor Table (GDT)각 블록그룹의 위치 및 크기 정보
Block Bitmap해당 그룹 내 블록 사용 여부 (확장 비트맵)
Inode Bitmap해당 그룹의 inode 사용 상태 표시
Inode Table파일/디렉토리의 메타데이터 저장
Data Blocks실제 파일 데이터 저장 공간
  • 구조 예시
    • 각 블록 그룹마다 있는 Block Bitmap이 “확장 비트맵” 역할을 수행
+---------------------------------------+
| Superblock (파일시스템 전체 정보)		|
| Group Descriptor Table (GDT)			|
+---------------------------------------+
| Block Group #0 | Block Group #1 | ...	|
|  ├─ Block Bitmap (확장 비트맵)			|
|  ├─ Inode Bitmap						|
|  ├─ Inode Table						|
|  └─ Data Blocks						|
+---------------------------------------+
  • 동작 예시
    • 1개의 블록 그룹에 8개의 데이터 블록이 있다고 가정 → 8비트 비트맵(10010110)을 사용한다면 8비트(1 byte)의 비트맵 한 개가 블록 그룹 내 8개의 데이터 블록에 대한 상태를 나타냄
    • 디스크가 커질수록 블록 수가 증가하므로, 파일 시스템은 여러 비트맵 블록을 “확장(extended)”해 관리
블록 번호상태의미
01사용 중
10비어 있음
20비어 있음
31사용 중
40비어 있음
51사용 중
61사용 중
70비어 있음
  • EXT 파일 시스템 관점에서 확장 비트맵의 특징
특징설명
계층 구조각 블록 그룹이 자체 비트맵을 가지므로, 전체 파일시스템 비트맵을 분산 관리
확장성(Extendibility)대규모 파일시스템에서도 비트맵 구조를 그룹 단위로 확장하여 사용
빠른 탐색특정 그룹 내 비어있는 블록을 빠르게 찾을 수 있음
일관성 보장각 그룹의 비트맵이 슈퍼블록과 동기화되어 저장됨
복구 효율성 향상손상 시 그룹 단위 복구 가능 (ext4의 저널링 활용)
  • 시각화
    파일을 새로 저장하면, 커널은 먼저 비트맵에서 "0"(빈 블록)을 탐색한다.

블록 사용 시 해당 비트를 "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파일 시스템
결과 | 대형 스토리지에서도 빠른 블록 할당/해제가 가능하도록 설계된 비트맵 확장 구조



KERNEL

https://kernel.org/

https://hancomdocs.com/ko/

https://hancomdocs.com/ko/



LIST

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;
}
  • head를 pointer가 아닌 node로 처리하기
    • 노드는 []로 묶기
  • head를 인자로 넘기기(전역변수 x)
  • 동적 메모리에 할당해야 한다는 고정관념 버리기
  • 하나의 프로세스 안에 수많은 threads
    • 유저가 코드를 잘못 썼을 때 프로세스 전체가 멈추면 안 됨
    • dummy head & dummy tail 구조
  • tail 없어도 됨: circular list
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

이중 연결 리스트

  • 타입에 의존적인 컨테이너 → generic하게 만들어야 함
    • void pointer 사용: 데이터와 리스트 분리

리스트에 데이터를 넣지 말고 데이터에 리스트를 넣기!

리눅스 커널에서 확인

lxr.linux.no: 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

list_entry

/**
 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 → 딕셔너리 구현 & 순회

포인터(pointer)

배열과 포인터의 관계

32비트 머신으로 설명

int main()
{
    int a[2];
    int *p = a; // 주소 변수 p에 a 담기
    return 0;
}
  • 대입이 된 걸 보니까 size도 같겠지? → 아님...
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*

  • decay: 배열의 이름은 "일반적으로" 배열의 첫 번째 원소의 시작 주소로 해석된다.
    • 예외
      • sizeof: original type이 가장 중요하기 때문
      • &(주소변수)

문제는 2차원 배열부터

int a[2][2] = {1,2,3,4};
int *p=a;

p[1][1]=10; // compile error

주소 예시
1a[0][0]1000
2a[0][1]1004
3a[1][0]1008
4a[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
모든 타입을 인자로 넘길 수 있어야 함
모든 타입을 리턴할 수 있어야 함

symbol로부터 해석 시작

  • int *p;
    • pointer to
    • int
  • int **p: int를 가리키는 포인터를 가리키는 포인터
  • pointer to
  • pointer to
  • int
  • int a[2];: int 2개로 된 배열 → 2차원 배열
    • array of 2
    • int
  • int a[2][2];
    • array of 2
    • array of 2
    • int
  • int *a[2]; ★★★
    • array of 2
    • pointer to
    • int
  • int *a[2]; ★★★
    • pointer to
    • array of 2
    • int
  • void foo() ★★★: void를 리턴하는 인자가 없는 함수
    • function() returning
    • void
  • void foo(int) ★★★: void를 리턴하는 int 인자 하나를 가지는 함수
    • function(int) returning
    • void
  • int foo(int) ★★★: int를 리턴하는 int 인자 하나를 가지는 함수
    • function(int) returning
    • 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]
    • pointer to
    • array of 2
    • pointer to
    • function() returning
    • pointer to
    • function() returning
    • pointer to
    • array of 2
    • int

간략화

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++의 등장

  • Object Oriented C
profile
2 B R 0 2 B

0개의 댓글