C언어 포인터 개념정리_1

2025. 6. 13. 01:17·C/Concept

 

 

 

 

 

 

 

 

 

🌟 구조1, 스택

더보기
#include <stdio.h>

int b() {
    printf("b\n");
    return 0;
}

int c() {
    printf("c\n");
    return 0;
}

int a() {
    printf("a\n");
    b();
    c();
    printf("a_end\n");
    return 0;
}

int main() {
    printf("main\n");
    a();
    return 0;
}

// main
// a
// b
// c
// a_end

 

① 프로그램 시작

  • main() 함수가 가장 먼저 실행

 

②main() 실행

  • "main" 출력
  • a() 호출

 

③  a() 실행

  • "a" 출력
  • b() 호출 → "b" 출력 후 종료
  • c() 호출 → "c" 출력 후 종료
  • "a_end" 출력
  • a() 종료 후 다시 main() 돌아감

 

④ 실행 흐름 (스택 관점)

  • 프로그램은 항상 main()에서 시작
  • main()은 a()를 호출 ( 이때, a()는 스택에 쌓이고 main()은 잠시 멈춤)
  • a() 안에서 b(), c()가 호출되고 종료
  • "a_end"까지 출력하고, a() 함수가 끝나면
  • → 스택에서 a()가 제거되고
  • → 이전 호출 지점이느 main ()의 다음 줄로 돌아감
  • main()의 나머지 실행이 끝나고, 프로그램이 종료

⑤ 스택 구조

  • 함수 호출이 쌓이고 풀리는 과정을 스택 구조 라고 부름
    함수가 종료되면 자동으로 스택에서 제거되며 이전 위치로 돌아가는 것도 이 구조 덕
  • Last In, First Out (LIFO) — 나중에 들어간 게 먼저 나감
  • 용도: 함수 호출 시 지역 변수 저장, 리턴 주소 기억

 

 

📌 중요한 포인트

  • 코드에서 함수가 위에 있든 아래에 있든 상관없음.
  • 실행은 항상 “누가 누구를 호출했느냐” 에 따라 흐름이 정해짐.
  • a() 아래에 main()이 정의돼 있어서 "돌아가는" 게 아니라,
    main()이 a()를 불렀기 때문에, a가 끝나면 main()으로 돌아가는 것.

 

 

※ main()이 가장 아래에 있는데 첫 번째로 호출되는 이유와 main()인 이유

  • C언어 공식적인 규칙이기 때문에
  • ISO 표준 (예: ISO/IEC 9899)에 따라 정의되어 있고 그 문서에서도 명확히 규정됨.
  • "A C program shall contain a function named main, which is the designated start of execution."
  • → “C 프로그램은 반드시 main이라는 이름의 함수를 포함해야 하며, 이 함수가 프로그램 실행의 시작점이다.”
  • 운영체제와 컴파일러가 “어디서부터 실행할지” 확실하게 알 수 있도록 하기 위해.
  • 초기 환경 셋업을 담당
  • 여러 함수가 존재해도 “main()이 진짜 시작이야!”라고 약속함으로써 가독성과 구조가 좋아짐.

 

※ C언어는 함수 어디에 있던 순서에 상관없는지

  • 파이썬과 C언어, 함수 처리 방식에 차이가 크기 때문!
항목 Python C 언어
함수 호출 시점 인터프리터가 위에서 아래로 한 줄씩 읽으며 실행함 → 아직 정의되지 않은 함수는 호출 불가 컴파일 시 모든 함수의 선언을 미리 처리함 → 아래에 정의된 함수도 사용 가능
예외 처리 함수가 나중에 나오면 NameError 발생 함수 선언만 되어 있다면, 정의는 나중에 나와도 상관없음.
#파이썬 예시

hello()  # 에러! 아직 hello가 정의되지 않았음

def hello():
    print("hi")
  • C 언어는 컴파일러가 전체 코드를 한 번에 분석해서 처리
  • 그래서 함수가 아래에 있어도, 컴파일러는 "이 함수는 나중에 정의되겠구나" 하고 인식
#include <stdio.h>

void hello();  // 함수 선언 (함수 원형)

int main() {
    hello();  // 아직 정의 안 됐지만 호출 가능
    return 0;
}

void hello() {
    printf("hi\n");
}

// void hello();는 함수 원형(prototype)이라고 불리며
// 컴파일러에게 "hello라는 함수가 있을 거니까 걱정하지 마" 하고 미리 알려주는 역할을 함
  • 함수 정의 순서는 중요하지 않음
  • 단, 먼저 호출되는 함수가 아래에 정의돼 있다면, 함수 원형 선언이 필요

 

 

 

🌟 구조2, & 주소연산자

더보기
#include <stdio.h>

void main()
{
    int iNum = 90;  // 정수형 변수 iNum에 90이라는 값을 저장
    
    // iNum 변수의 값을 출력 (%d는 정수를 출력하는 형식 지정자)
    printf("iNum 값은: %d\n", iNum);
    
    // iNum 변수의 메모리 주소를 출력 (&iNum은 iNum의 주소를 의미)
    printf("iNum 주소는: %p\n", &iNum);
}

 

① 포인터란?

  • 포인터는 다른 변수의 메모리 주소를 저장하는 특별한 변수
  • 실제 데이터가 저장된 위치를 가르키는 "주소록" 역할

 

② & (주소 연산자, Ampersand)

  • 변수명과 함께 사용하며 변수의 메모리 주소를 얻을 때 사용하는 연산자
  • 변수가 참조하는 메모리 공간의 첫 byte 주소를 가져옴
  • 변수 앞에 &를 붙이면, 그 변수가 메모리에서 차지하는 공간의 시작 주소를 반환
  • printf( ) 함수를 사용하여 출력할 경우, %p %x 포멧을 사용하여 메모리 주소값을 출력 가능
// 실행결과 예상

iNum 값은: 90
iNum 주소는: 0x7fff5fbff6ac (실제 주소는 실행할 때마다 다를 수 있음)

 

③ 메모리구조 (거대한 아파트 단지라 예시)

  • 각 아파트 호수가 메모리 주소
  • 각 아파트에 사는 사람이 저장된 값
  • iNum이라는 변수는 특정 아파트 호수(주소)에 90 이라는 값 저장

 

④ 형식 지정자

  • %d: 정수 값 출력할 때 사용
  • %p: 포인터(주소) 값을 16진수로 출력할 때 사용

 

※ 모든 선언된 변수명 앞에 &연사를 붙여 해당 변수가 참조하는메모리 공간의 첫번째 바이트를 식별 할 수 있는 

    고유한 메모리 주소값을 참조하여 "값" 으로 사용할 수 있다.  <링크>

// 예시

int num = 100;

printf("%d", num);    // 100 출력 (변수의 값)
printf("%p", &num);   // 0x0061FF22 출력 (주소를 값으로 사용)

int *ptr = &num;      // 주소를 다른 변수에 저장
printf("%p", &ptr);   // 0x0061FF22 출력이 됨

 

 

 

🌟 내가 보려고 적은 주소연산자

더보기

주소 연산자의 핵심

 

1. 주소연산자란?

변수가 메모리에서 저장된 위치(주소)를 알려주는 연산자.

변수: 집에 사는 사람

변수의 값: 그 사람의 이름

주소 연산자: 그 집의 주소를 알려주는 것

 

2. 주소연산자의 동작 원리

#include <stdio.h>

void main()
{
    int iNum = 90;  // 변수 선언 및 초기화
    
    printf("iNum의 값: %d\n", iNum);        // 90 출력
    printf("iNum의 주소: %p\n", &iNum);     // 메모리 주소 출력 (예: 0x7fff1234)
}

 

① int uNum = 90;

  • 컴퓨터가 메모리에서 4바이트 공간을 찾음
  • 그 공간에 90이라는 값을 저장
  • 그 공간에 iNum이라는 이름을 붙임

 

②%iNum

  • iNum이 저장된 메모리 공간의 시작 주소를 반환
  • 예) 1000번지라면 1000을 반환
  • 주소는 16진수로 표현, 메모리 주소를 간결하게 표현하기 위해

 

※ 주소는 시행할 때마다 다를 수 있음

  • 운영체제가 프로그램에게 메모리를 동적으로 할당하기 때문

 

※ 데이터 타입별 메모리 크기

int iNum = 90;      // 4바이트 사용
char cChar = 'A';   // 1바이트 사용
double dNum = 3.14; // 8바이트 사용

printf("int 주소: %p\n", &iNum);    // 4바이트 공간의 시작 주소
printf("char 주소: %p\n", &cChar);  // 1바이트 공간의 시작 주소
printf("double 주소: %p\n", &dNum); // 8바이트 공간의 시작 주소

 

3. 주소연산자의 활용

#include <stdio.h>

// 값만 받는 함수 (원본 변경 불가)
void changeValue1(int num)
{
    num = 100;  // 복사본만 변경됨
}

// 주소를 받는 함수 (원본 변경 가능)
void changeValue2(int *pNum)
{
    *pNum = 100;  // 원본이 변경됨
}

void main()
{
    int iNum = 90;
    
    printf("원본 값: %d\n", iNum);        // 90
    
    changeValue1(iNum);                   // 값을 복사해서 전달
    printf("함수1 후 값: %d\n", iNum);    // 90 (변경되지 않음)
    
    changeValue2(&iNum);                  // 주소를 전달
    printf("함수2 후 값: %d\n", iNum);    // 100 (변경됨)
}

 

4. 주소연산자 사용시 주의사항

① 형식 지정자

// 올바른 사용
printf("주소: %p\n", &iNum);   // %p 사용

// 잘못된 사용
printf("주소: %d\n", &iNum);   // %d는 정수용이므로 부적절

 

② 상수에서는 사용 불가

int iNum = 90;
printf("%p\n", &iNum);  // 가능: 변수의 주소
printf("%p\n", &90);    // 불가능: 상수는 메모리 위치가 없음

 

5. 요약

  • 메모리 위치 파악: 변수가 어디에 저장되어 있는지 알려줌
  • 포인터의 기초: 포인터 변수에 저장할 주소값 생성
  • 함수간 데이터 전달: 원본 데이터를 직접 조작할 수 있게 함
  • 메모리 관리: 프로그램이 메모리를 어떻게 사용하는지 이해할 수 있게 함
  • 주소는 메모리에서 데이터가 저장된 위치를 나타내는 번호

 

 

 

🌟 구조 3, 메모리

더보기

ⓛ 메모리 영역 구분

각 함수는 독립된 스택 공간을 가지고, 서로 직접 접근 불가

 

② 단계별 코드 실행 과정

// 초기 상태(프로그램 시작)

int num = 0;  // 변수 선언 및 초기화

┌───────────────────────────────────────────┐    
│        메모리 주소: 0x7fff1000             │
├─────────────────────────────────────      │
│  바이트 단위로 보면:                       │
│  [00000000][00000000][00000000][00000000] │
│   ↑                                       │
│   4바이트 = 32비트 = 0 (십진수)            │
│                                           │
│  변수명: num                              │
│  값: 0                                    │
│  주소: 0x7fff1000                         │
└───────────────────────────────────────────┘
// scanf() 함수 호출

scanf("%d", &num);  // 사용자 입력 대기
  • scanf 함수가 &num을 통해 주소값 0x7fff1000을 받음

사용자가 "90" 입력 후 Enter

  • 문자열 "90 읽기": 키보드에서 '9', '0' 문자 읽음
  • 문자  숫자 변환: "90" 문자열을 90 정수로 변환
  • 이진수 변환: 90(십진수) 01011010(이진수)
  • 메모리 저장: 받은 주소 (0x7fff1000) 에 저장
변화 전:
┌───────────────────────────────────────────┐
│        메모리 주소: 0x7fff1000             │
├───────────────────────────────────────────┤
│  [00000000][00000000][00000000][00000000] │
│   값: 0                                   │
└───────────────────────────────────────────┘

변화 후:
┌───────────────────────────────────────────┐
│        메모리 주소: 0x7fff1000             │
├───────────────────────────────────────────┤
│  [01011010][00000000][00000000][00000000] │
│   값: 90                                  │
└───────────────────────────────────────────┘

 

③ 실제 메모리 변화

#include <stdio.h>

int main(void)
{
    int num = 0;
    
    // 입력 전 상태 확인
    printf("=== 입력 전 ===\n");
    printf("num의 값: %d\n", num);
    printf("num의 주소: %p\n", &num);
    
    // 메모리 내용을 바이트 단위로 확인
    unsigned char *ptr = (unsigned char*)&num;
    printf("메모리 내용 (바이트 단위): ");
    for(int i = 0; i < 4; i++) {
        printf("%02X ", ptr[i]);
    }
    printf("\n\n");
    
    // 사용자 입력
    printf("정수를 입력하세요: ");
    scanf("%d", &num);
    
    // 입력 후 상태 확인
    printf("\n=== 입력 후 ===\n");
    printf("num의 값: %d\n", num);
    printf("num의 주소: %p (변하지 않음)\n", &num);
    
    // 메모리 내용을 바이트 단위로 확인
    printf("메모리 내용 (바이트 단위): ");
    for(int i = 0; i < 4; i++) {
        printf("%02X ", ptr[i]);
    }
    printf("\n");
    
    return 0;
}
// 실행 결과 예시

=== 입력 전 ===
num의 값: 0
num의 주소: 0x7fff5fbff6ac
메모리 내용 (바이트 단위): 00 00 00 00 

정수를 입력하세요: 90

=== 입력 후 ===
num의 값: 90
num의 주소: 0x7fff5fbff6ac (변하지 않음)
메모리 내용 (바이트 단위): 5A 00 00 00

// 주소는 변화지 않음
// 바이트 단위 덮어쓰기

 

④ scanf() 함수의 메모리 변경 과정

  • 주소 수신: &num으로 받은 주소 0x7fff1000 확인
  • 입력 파싱: "90" 문자열을 90 정수로 변환
  • 이진 변환: 90 → 01011010 (이진수)
  • 메모리 쓰기: 해당 주소의 4바이트에 새 값 저장
  • 완료: 이전 값(0)은 완전히 사라지고 새 값(90)만 남음
  • 변수의 주소는 절대 변하지 않음
  • 변하는 것은 오직 그 주소에 저장된 값
  • 새로운 값이 이전 값을 완전히 덮어 씀
  • 이 모든 과정은 scanf 함수 내부에서 자동으로 처리
// 변화 전 초기상태

int num = 0; 선언 시

메모리 주소 0x7fff1000에 저장된 값:
00000000 00000000 00000000 00000000
↑                                ↑
32비트 (4바이트) 모두 0           = 십진수 0


// 변화 후 

scanf("%d", &num); 으로 90 입력 시

메모리 주소 0x7fff1000에 저장된 값:
00000000 00000000 00000000 01011010
↑                         ↑
상위 24비트는 0             하위 8비트가 01011010 = 십진수 90
90을 이진수로 변환하는 과정: 

90 ÷ 2 = 45 나머지 0  ← 나머지 0 // 이진수는 2를 기준으로 하는 수 체계이기 때문에 2로 나눔
45 ÷ 2 = 22 나머지 1  ← 나머지 1  
22 ÷ 2 = 11 나머지 0  ← 나머지 0
11 ÷ 2 = 5  나머지 1  ← 나머지 1
5 ÷ 2 = 2   나머지 1  ← 나머지 1
2 ÷ 2 = 1   나머지 0  ← 나머지 0
1 ÷ 2 = 0   나머지 1  ← 나머지 1

나머지를 아래에서 위로 읽으면: 1011010

아래에서 위로 읽으면: 1011010
8비트로 맞추면: 01011010
32비트로 맞추면: 00000000 00000000 00000000 01011010


변화 전:  00000000 00000000 00000000 00000000
변화 후:  00000000 00000000 00000000 01011010
         ↑        ↑        ↑        ↑
         변화없음   변화없음   변화없음   변화됨
         

주소:     0x7fff1000  0x7fff1001  0x7fff1002  0x7fff1003
변화 전:  [00000000]  [00000000]  [00000000]  [00000000]
변화 후:  [01011010]  [00000000]  [00000000]  [00000000]
         ↑ 여기만     ↑ 변화없음   ↑ 변화없음   ↑ 변화없음
           변화됨

└ Little Endian 방식

  • 낮은 자리수가 낮은 주소에 저장
  • 90 = 01011010이 첫 번째 바이트에 저장
  • 나머지 3 바이트는 모두 0

 

// 시각적 확인 코드

#include <stdio.h>

int main(void)
{
    int num = 0;
    
    printf("=== 초기 상태 ===\n");
    printf("십진수: %d\n", num);
    printf("이진수: ");
    
    // 32비트를 8비트씩 나누어 출력
    for (int i = 31; i >= 0; i--) {
        printf("%d", (num >> i) & 1);
        if (i % 8 == 0 && i != 0) printf(" ");
    }
    printf("\n\n");
    
    printf("90을 입력하세요: ");
    scanf("%d", &num);
    
    printf("\n=== 변화 후 ===\n");
    printf("십진수: %d\n", num);
    printf("이진수: ");
    
    // 32비트를 8비트씩 나누어 출력
    for (int i = 31; i >= 0; i--) {
        printf("%d", (num >> i) & 1);
        if (i % 8 == 0 && i != 0) printf(" ");
    }
    printf("\n");
    
    return 0;
}


// 실행 결과

=== 초기 상태 ===
십진수: 0
이진수: 00000000 00000000 00000000 00000000

90을 입력하세요: 90

=== 변화 후 ===
십진수: 90
이진수: 00000000 00000000 00000000 01011010

 

 

 

 

 

 

 

 

 

 

 

미리보기용

'C > Concept' 카테고리의 다른 글

C언어 형변환 개념정리  (0) 2025.06.17
C언어 포인터 개념정리_2  (0) 2025.06.16
C언어 소스코드 개념정리  (2) 2025.06.11
'C/Concept' 카테고리의 다른 글
  • C언어 형변환 개념정리
  • C언어 포인터 개념정리_2
  • C언어 소스코드 개념정리
eull
eull
eull 님의 블로그 입니다.
  • eull
    eull 님의 블로그
    eull
  • 전체
    오늘
    어제
    • 개발 환경 (31)
      • MYSQL_Workbench (1)
        • setting (1)
      • Linux_Ubuntu (2)
        • Task (1)
        • Setting (1)
      • C (19)
        • Concept (4)
        • Task (8)
        • Project (1)
        • Study (5)
        • Setting (1)
      • C++ (1)
        • Study (0)
        • Concept (1)
      • Python (6)
        • Task (4)
        • Project (2)
      • 일상 (1)
      • Setting (1)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.3
eull
C언어 포인터 개념정리_1
상단으로

티스토리툴바