[C] 10. 함수와 포인터 기본

Taeil Nam·2022년 6월 15일
0

C

목록 보기
10/18
post-thumbnail

1. 함수

사용 이유

  1. 불필요한 반복 제거

    • 동일한 코드를 반복 사용할 경우, 코드의 가독성이 떨어지고 유지보수가 어려움.
  2. 특정 기능 추가 및 사용

    • 자주 사용하는 특정 기능을 함수로 만들어서 필요할 때마다 사용.
      Ex) printf(), scanf(), getchar(), putchar() ...

함수 사용 전

#include <stdio.h>

#define WIDTH   20	// 기호적 상수 WIDTH 선언
#define NAME    "Tae-Il Nam"	// 기호적 상수 NAME 선언
#define ADDRESS "Seoul, Korea"	// 기호적 상수 ADDRESS 선언

int main()
{
    for (int i = 0; i < WIDTH; ++i)	// WIDTH 값(20) 만큼 for문 실행. 아래 for문과 동일.
        printf("*");
    printf("\n");
    printf("    %s    \n", NAME);		// NAME 값 출력.
    printf("    %s    \n", ADDRESS);	// ADDRESS 값 출력.
    for (int i = 0; i < WIDTH; ++i)	// WIDTH 값(20) 만큼 for문 실행. 위 for문과 동일.
        printf("*");
	printf("\n");
    
    return 0;
}

함수 사용 후

#include <stdio.h>

#define WIDTH   20
#define NAME    "Tae-Il Nam"
#define ADDRESS "Seoul, Korea"

void print_star(int n_star)	// print_star 함수 정의 (정수형 매개변수 = n_star)
{
    for (int i = 0; i < n_star; ++i)	// n_star 값(20) 만큼 for문 실행.
        printf("*");
    printf("\n");
}

int main()
{
    print_star(WIDTH);	// print_star 함수 호출. 매개변수에 WIDTH 값(20) 전달.
    printf("    %s    \n", NAME);
    printf("    %s    \n", ADDRESS);
    print_star(WIDTH);	// print_star 함수 호출. 매개변수에 WIDTH 값(20) 전달.

    return 0;
}

매개변수(Parameter)와 인수(Argument)

매개변수

  • 함수에 전달된 값이 저장되는 변수.

인수

  • 함수를 호출할 때 전달하는 값.
#include <stdio.h>

#define WIDTH   20
#define NAME    "Tae-Il Nam"
#define ADDRESS "Seoul, Korea"

void print_star(int n_star)	// 매개변수 = (int n_star).
{
    for (int i = 0; i < n_star; ++i)
        printf("*");
    printf("\n");
}

int main()
{
    print_star(WIDTH);	// print_star 함수 호출. 인수 = WIDTH(20).
    printf("    %s    \n", NAME);
    printf("    %s    \n", ADDRESS);
    print_star(WIDTH);	// print_star 함수 호출. 인수 = WIDTH(20).

    return 0;
}

함수의 프로토타입(Prototype)

  • 함수는 호출되기 전에 정의 되어있어야 함. (main 함수보다 먼저 정의 필요)
  • 함수를 만들 수록 main 함수는 밑으로 내려가게 되며, 이로인해 코드 가독성이 떨어짐.
  • 프로토타입을 사용하면 함수를 main 함수 밑에 사용할 수 있음.
💡 함수의 내용 없이 프로토타입만 선언 한다면? (링킹 에러)
- 컴파일에러는 발생하지 않으나 링킹 과정에서 에러 발생.
- 컴파일러는 프로토타입이 있으니 함수가 있을 것이라고 판단.
- 실제로 함수의 내용을 프로토타입과 매핑하는 링킹 과정에서, 함수의 내용을 찾지 못해 에러 발생.
#include <stdio.h>

#define WIDTH   20
#define NAME    "Tae-Il Nam"
#define ADDRESS "Seoul, Korea"

void print_star(int n_star);	// print_star 함수의 프로토타입. (세미콜론';' 입력 필수)

int main()
{
    print_star(WIDTH);	
    printf("    %s    \n", NAME);
    printf("    %s    \n", ADDRESS);
    print_star(WIDTH);	

    return 0;
}

void print_star(int n_star)	// print_star 함수 내용.
{
    for (int i = 0; i < n_star; ++i)
        printf("*");
    printf("\n");
}

함수의 반환값과 자료형

반환값(return)

  • 함수가 실행되어 만들어진 값을 반환.
#include <stdio.h>

int print_sum(int n1, int n2);	// print_sum 함수의 프로토타입.

int main()
{
	int a = 1;
    int b = 2;
	printf("%d", print_sum(a, b));
    
    return 0;	// main() 함수의 반환값 = 0.
}

int print_sum(int n1, int n2)	// print_sum 함수 정의.
{
	int sum = n1 + n2;	// 매개변수 n1, n2를 더하고 정수형 변수 sum에 대입.
    
    return sum;	// print_sum 함수의 반환값 = sum의 값(정수).
}

자료형

  • 반환값(return)이 없는 함수의 자료형 = void
  • 반환값(return)이 있는 함수의 자료형 = 반환값(return)의 자료형(int or char or float ...)
#include <stdio.h>

#define WIDTH   20
#define NAME    "Tae-Il Nam"
#define ADDRESS "Seoul, Korea"

void print_star(int n_star);

int main()	// main() 함수의 반환값(return) 자료형 = int
{
    print_star(WIDTH);	
    printf("    %s    \n", NAME);
    printf("    %s    \n", ADDRESS);
    print_star(WIDTH);	

    return 0;	// main() 함수의 반환값 = 0(정수).
}

void print_star(int n_star)	// 반환값(return)이 없는 함수의 반환값 자료형 = void.
{
    for (int i = 0; i < n_star; ++i)
        printf("*");
    printf("\n");
}

변수의 영역과 지역변수

  • 변수는 변수를 사용할 수 있는 영역이 존재. (영역 구분 = {}(중괄호))
  • 특정 영역에서 선언된 변수는 해당 영역에서만 사용 가능. (지역 변수)
    Ex) func1 함수에서 선언된 변수는, func2 함수에서 사용 불가능.
  • 영역에 속하지 않는 곳에서 선언된 변수는, 소스 파일의 어디서든 사용 가능. (전역 변수)
  • 큰 영역에서 선언된 변수와 작은 영역에서 선언된 변수의 이름이 같아도, 실제 각 변수의 Memory 주소는 다르기 때문에 다른 변수라고 볼 수 있음.
#include <stdio.h>

int main()
{
    int a = 1;	// main() 함수의 지역 변수 a 선언.

    printf("%d\n", a);	// main() 영역의 변수 a의 값 출력.
    printf("%p\n", a);	// main() 영역의 변수 a의 실제 Memory 주소 출력.(포인터) 

    {	// 내부 영역 추가.
        int a = 2;	// 내부 영역의 지역 변수 a 선언.

        printf("%d\n", a);	// 내부 영역의 변수 a의 값 출력.
    	printf("%p\n", a);	// 내부 영역의 변수 a의 실제 Memory 주소 출력.(포인터) 
    }

    printf("%d\n", a);	// main() 영역의 변수 a의 값 출력.
    printf("%p\n", a);	// main() 영역의 변수 a의 실제 Memory 주소 출력.(포인터) 

    return 0;
}

결과

💡 프로그램을 실행할 때마다 Memory 주소가 달라지는 이유.
- 프로그램 실행시, 운영체제(OS)가 그때그때 상황에 맞게 변수의 Memory 주소 할당.

재귀 호출(Recursion)

  • 함수가 자기 자신을 호출하는 것.
  • 재귀 호출이 수행될 때마다, 함수의 매개변수도 새로 생김.
  • Memory를 많이 사용. (비효율적)
  • 계산의 중복이 생김.
#include <stdio.h>

void my_func(int n);	// my_func 함수 프로토타입.

int main()
{
    my_func(1);	//my_func 함수 호출.

    return 0;
}

void my_func(int n)
{
    printf("Level %d, address of variable n = %p\n", n, &n);	// n의 값과 Memory 주소 출력.

    if (n <= 4)	n이 4보다 작거나 같을 때까지
        my_func(n + 1);	// 매개 변수에 1을 더해서 재귀 호출.
}

결과


2. 팩토리얼 예제

  • 반복문, 재귀 호출 두 가지 방법 예제.
#include <stdio.h>

/*
    factorial : 3! = 3 * 2 * 1
    5! = 5 * 4!
    0! = 1
*/

long loop_factorial(int n);			// 반복문 함수.
long recursive_factorial(int n);	// 재귀 호출 함수.

int main()
{
    int num = 5;

    printf("%d\n", loop_factorial(num));
    printf("%d\n", recursive_factorial(num));

    return 0;
}

long loop_factorial(int n)
{
    long sum = 1;

    for (int i = 1; i <= n; ++i)
        sum *= i;

    return sum;
}

long recursive_factorial(int n)
{
    if (n > 0)	// n이 0보다 클 경우.
    {
        return n * recursive_factorial(n - 1);	// n 이 0이 될 때까지 재귀 호출.
    }
    else
        return 1;
}

3. 피보나치 수열 예제

#include <stdio.h>

int fibonacci(int number);	// fibonacci 함수 프로토타입.

int main()
{
    for (int count = 1; count < 13; ++count)
        printf("%d ", fibonacci(count));

    return 0;
}

int fibonacci(int number)
{
    if (number > 2)	// number 값이 2보다 큰 경우.
        return fibonacci(number - 1) + fibonacci(number - 2);
    else
        return 1;	// number 값이 2보다 작거나 같은 경우.
}

4. 포인터 기본

변수 선언 상세 내용

  • 변수 선언 = memory 공간을 할당 받는 것.
  • 자료형 = 할당 받을 memory 공간의 크기 및 저장될 값의 형태 지정.
  • memory 주소 = memory 공간 식별 값.
  • 변수 = memory 주소의 별칭.
#include <stdio.h>

int main()
{
    int a;	// 정수가 저장될 4바이트 memory 공간을 변수 a에 할당.
    int b;	// 정수가 저장될 4바이트 memory 공간을 변수 b에 할당.
    int c;	// 정수가 저장될 4바이트 memory 공간을 변수 c에 할당.

    a = 7;	// a라는 4바이트 memory 공간에 정수 7 저장.
    b = 8;	// b라는 4바이트 memory 공간에 정수 8 저장.
    c = a + b;	// c라는 4바이트 memory 공간에 정수 a + b 연산 값 저장.

    return 0;
}



포인터

  • 변수의 실제 memory 주소를 가리키는 것.

포인터 변수와 주소 연산자

1. 포인터 변수
- memory 주소 값을 저장하는 변수.
- 변수 선언시 앞에 '*' 기입. (Asterisk)
- 포인터 변수의 자료형은, memory 주소를 가져올 대상 변수의 자료형과 동일해야 함.
- 포인터 변수도 값 저장을 위한 memory 공간을 가짐.

2. 주소 연산자(Address-of operator)
- '&' 를 변수 앞에 기입.
- 변수의 실제 memory 주소 값 반환.

#include <stdio.h>

int main()
{
    int a = 7;
    int *a_ptr = &a;	// 포인터 변수 a_ptr 에 a의 memory 주소 값 대입.
    					// 변수 a의 자료형이 int 이므로, 포인터 변수 자료형도 동일하게 int로 지정.

    return 0;
}



포인터 변수 사용(Indirection)

  • 포인터 변수 앞에 '*' 을 붙여서 사용.
  • 포인터 변수 선언시 사용한 '*'(asterisk)과 다름.
#include <stdio.h>

int main()
{
    int a = 7;
    int *a_ptr = &a;	// 포인터 변수 a_ptr 에 변수 a의 memory 주소 값 대입.

    *a_ptr = 8;	// 변수 a의 memory 주소에 해당하는 공간에 8 대입.

    printf("%d", a);	// 변수 a 값 출력 = 8.

    return 0;
}



포인터의 기본적인 사용 방법

코드

#include <stdio.h>

int main()
{
    int a, b;	// 정수형 변수 a, b 선언.

    a = 123;	// 변수 a에 값 123 대입.

    int* a_ptr; // 포인터 변수 a_ptr 선언. * : asterisk

    a_ptr = &a; // 포인터 변수 a_ptr에 변수 a의 memory 주소 대입. & : address-of operator

    printf("%d %d %p\n", a, *a_ptr, a_ptr);
    // 변수 a의 값, 포인터 변수 a_ptr의 indirect 값(변수 a의 값), 포인터 변수 a_ptr의 값(memory 주소) 출력.
    // 포인터 변수의 값(memory 주소)을 출력하기 위한 형식지정자 = %p.

    *a_ptr = 314;   // 변수 a의 memory 주소에 해당하는 공간에 314 대입.

    printf("%d %d %p\n", a, *a_ptr, a_ptr);
    // 변수 a의 값, 포인터 변수 a_ptr의 indirect 값(변수 a의 값), 포인터 변수 a_ptr의 값(memory 주소) 출력.

    return 0;
}

결과



포인터 변수 선언 코딩 스타일

  • 포인터 관련 코딩 스타일은, 같이 협업하는 동료들과 상의하여 결정.

포인터 변수 선언 스타일

  1. '*'(asterisk)를 자료형에 붙이기
  • C++ 에서 권고하는 방법.
#include <stdio.h>

int main()
{
    int* a;	// 자료형 쪽에 '*' 기입.
    int* b;	// 자료형 쪽에 '*' 기입.

    return 0;
}
  1. '*'(asterisk)를 변수명에 붙이기
  • 일반적으로 가르칠 때 알려주는 방법.
#include <stdio.h>

int main()
{
    int *a;	// 변수명 쪽에 '*' 기입.
    int *b;	// 변수명 쪽에 '*' 기입.

    return 0;
}

NULL 포인터와 런타임 에러

  • 실제로 포인터를 사용할 때, 포인터 변수에 memory 주소를 저장할지 말지는 대부분 런타임에 결정됨.
  • 포인터 변수에 memory 주소가 제대로 저장되면 상관없으나, 저장되지 않은 경우 추후 해당 포인터 변수 사용시 런타임 에러 발생. (memory 주소가 없는 포인터 변수이므로)
  • 런타임 에러 방지를 위해, 일반적으로 포인터 변수 선언시 NULL 값을 대입하여 초기화.
  • NULL = 0과 같음.
  • C++ 에서는 nullptr이 존재.
#include <stdio.h>

int main()
{
    int* a = NULL;	// 포인터 변수 a의 값을 NULL로 초기화.
    int* b = NULL;	// 포인터 변수 b의 값을 NULL로 초기화.

    return 0;
}

포인터 변수의 크기

  • 포인터 변수의 크기는 정해져 있다.

코드

#include <stdio.h>

int main()
{
    char a;		// 1 바이트 크기의 정수형 변수 a 선언.
    float b;	// 4 바이트 크기의 실수형 변수 b 선언.
    double c;	// 8 바이트 크기의 실수형 변수 c 선언.

    char* ptr_a = &a;		//	변수 a의 memory 주소를 포인터 변수 ptr_a 에 대입.
    float* ptr_b = &b;		//	변수 b의 memory 주소를 포인터 변수 ptr_b 에 대입.
    double* ptr_c = &c;		//	변수 c의 memory 주소를 포인터 변수 ptr_c 에 대입.

    printf("%zd %zd %zd\n", sizeof(a), sizeof(b), sizeof(c));					// 변수 a, b, c의 memory 크기 출력.
    printf("%zd %zd %zd\n", sizeof(ptr_a), sizeof(ptr_b), sizeof(ptr_c));		// 포인터 변수 ptr_a, ptr_b, ptr_c의 memory 크기 출력.
    printf("%zd %zd %zd\n", sizeof(&a), sizeof(&b), sizeof(&c));				// 변수 a, b, c의 포인터 memory 크기 출력.
    printf("%zd %zd %zd\n", sizeof(char*), sizeof(float*), sizeof(double*));	// 포인터 변수 char, float, double의 memory 크기 출력.

    return 0;
}

결과

  1. 32 bits 시스템(x86)
    • 32 bits 시스템의 포인터 크기 = 4 bytes
    • 32 bits = 레지스터의 크기.
    • CPU의 memory 접근을 위한 주소의 크기가 32 bits(4 bytes) 만큼 가능.

  2. 64 bits 시스템 (x64)

    - 64 bits 시스템의 포인터 크기: 8 bytes
    - 64 bits = 레지스터의 크기.
    - CPU의 memory 접근을 위한 주소의 크기가 64 bits(8 bytes) 만큼 가능.

포인터형 매개변수

코드

  • 변수 a, b의 값을 바꿔서 출력하기.
#include <stdio.h>

void swap(int* a_ptr, int* b_ptr)	// 변수 a, b의 값을 바꿔주는 swap() 함수 정의.
{									// 포인터형 매개변수로 a, b의 주소 값을 받음.
    int temp = *a_ptr;	// temp 변수에 변수 a의 값 대입.

    *a_ptr = *b_ptr;	// 변수 a에 변수 b의 값 대입.
    *b_ptr = temp;		// 변수 b에 변수 temp의 값 대입.
}

int main()
{
    int a = 123;	// 정수형 변수 a 초기화 = 123.
    int b = 456;	// 정수형 변수 b 초기화 = 456.

    swap(&a, &b);	// swap() 함수 호출. 인자 = 변수 a, b의 주소값.
    printf("%d %d\n", a, b);	// 변수 a, b의 값 출력.
    
    return 0;

}

🚩 출처 및 참고자료 : 홍정모의 따라하며 배우는 C 언어 (따배씨)

0개의 댓글