배움 저장소

홍정모의 따라하며 배우는 C언어 9 함수 본문

Programming Language/C

홍정모의 따라하며 배우는 C언어 9 함수

시옷지읏 2021. 11. 20. 20:52

9.2 함수의 프로토타입

// prototype
void print_multiple_chars( );
void print_multiple_chars(char, int, bool);

int main()
{
    print_multiple_chars('*', WIDTH, true);
}

void print_multiplechars(char c, int width, bool DoesNewlineNeeded)
{
	// sth....
}

함수의 프로토타입에서 매개변수를 지정하지 않고 비워놓아도 상관없다. 혹은 자료형만 표기할 수도 있다.

 

9.3 함수의 자료형과 반환값

정수형 반환 자료형은 생략 가능하다. 반환 자료형이 없다면 정수형으로 가정한다.

 

9.5 지역변수와 스택

int main()
{
    // Main scope A
	int a = 10;
    printf("%d", a);
    printf("%p", a);
    
    {
        // Local Scope A
        int a = 5;
    	printf("%d", a);
        printf("%p", a);
    }
    printf("%d", a);
    printf("%p", a);
}

 변수가 만들어지면 Stack에 쌓는다. 만약 새로운 Scope가 생기고 변수가 Stack에 쌓이면 Scope가 끝날 때 해당 Scope내에 정의된 변수들은 사라진다.

 위 예시를 보자. main( ) 함수 내에 또 다른 Scope가 { } 로 정의되어 있다. 외부에 있는 a가 스택에 쌓이고 그 위에 내부 scope의 a가 쌓인다. Stack에 a가 2개 있으며 서로 다른 주소를 가지고 있다. 사용시 맨 위에 쌓여있는 a를 사용한다. 

 

9.9 이진수 변환 예제

Array에 저장하기

// Calculate Size of Binary Array
int num = 130;
int count = 0;
for(int i=num; i>0; i/=2 )
{
    printf("%d\n", i);
    count++;
}
printf("count=%d num=%d\n", count , num);
int *binary = malloc(sizeof(int)*count);

// Put Each Element in Binary Array
for(int i=count-1; i>=0; i--)
{
    binary[i] = num%2 ;
    num /=2;
}

// Print Array
printf("result\n{ ");
for(int i=0; i<count; ++i)
{
    printf("%d ",binary[i]);
}
printf("}\n");

free(binary);

 

Recursion && print

void binary(int n)
{
    if(n==0)
    {
        printf("0");
        return;
    }
    else if(n==1)
    {
        printf("1");
        return;
    }

    binary(n/2);
    printf("%d",n%2);
}

 

9.10 피보나치 예제와 재귀 호출의 장단점

int fibonacci(int n)
{
    if(n<=0)
    {
        printf("Error!!\n");
        return INT_MIN;
    }

    if(n==1 || n==2)
    return 1;

    // Double Recursion
    return fibonacci(n-1) + fibonacci(n-2);
}

같은 값을 여러번 계산하여 비효율적이다. DP를 사용하면 된다.

	int num = 20;
	int* fibonacci = (int*)malloc(sizeof(int) * num);
	fibonacci[0] = 1;
	fibonacci[1] = 1;

	for (int i = 2; i < num; ++i)
	{
		fibonacci[i] = fibonacci[i - 1] + fibonacci[i - 2];
	}

 

9.11 헤더 파일 만드는법

컴파일 + 링킹 과정을 생략하려면 Visual Studio를 사용하자

// Print_Function.h
// #include <stdio.h> // Library is not needed
#pragma once
void print_str(char* chr);

// Print_Function.c
#include <stdio.h>  // Put this Library on here
#include "Print_Function.h"
void print_str(char* chr)
{
    printf("%s", chr);
}

// main.c
#include "Print_Function.h"
int main()
{
	char sentence[] = "Nice to meet you!";
	print_str(sentence);
}

- 위 코드에서 <stdio.h>헤더는 Print_Function.c에서 사용하고 있다. 

두 가지 중 하나를 선택할 수 있다. main.c에서 <stdio.h>헤더를 포함시켜주거나, Print_Function.h에서 <stdio.h>를 포함시켜주어야 한다. 따라서 Print_Function.c에서 <stdio.h>를 지우고 main.c에서 포함시켜주어도 정상적으로 작동한다.

main.c에서 헤더를 포함하는 방법을 권장한다.

 

- 코드파일 Print_Function.c에서 헤더파일 Print_Function.h을 포함시켜주는 이유

Visual Studio가 header에 선언되어있는 함수명과 변수명을 가져와 자동완성 기능을 지원해준다. 편리하다.

 

- prototype이 정의된 헤더만 포함시켰는데도 구현 부분이 잘 동작하는 이유

>> 컴파일러가 찾아서 연결시켜준다. (Visual Studio가 해준다)

 

- 함수의 정의와 선언을 구분된 파일에 보관하는 이유(헤더파일과 실행파일을 나누는 이유)

>> 함수는 한 번만 정의할 수 있다.(함수의 { } 바디는 하나이다) 함수를 여러 번 정의하는 것은 오류이다.

// A.h 
#include "B.h"
void functionA()
{
	printf("FunctionA is Working\n");
    functionB();
}

// B.h
void functionB()
{
	printf("FunctionB is Working\n");
}

// main.c
#include "A.h"
#include "B.h"

int main()
{
    functionA();
    functionB();
}

 위와 같은 상황에서 #include의 기능은 해당 파일의 코드를 가져와 복사+붙여넣기를 할 뿐이다. 그러면 B.h에 속해있는 functionB( ) 함수는 main.c 파일에서 두 개의 Body를 가지고 에러를 만든다.

 함수의 Prototype은 재정의되어도 문제가 없다. 중복되는 정의를 막기 위하여 Prototype이 필요하고 이에따라 header 파일을 사용하면 유용하다.

 

참고

1. Visual Studio에서 자동으로 Header FilesSource Files를 구분한다. 이는 Windows Explorer에는 반영되지 않는다. 

   이를 Filter라고 한다. Visual Studio Explorer에서만 확인이 가능하며, Filter를 새로만들 수 있다.

   Resource File은 Img와 같은 참조 파일을 넣는다.

 

2.c++에서 헤더 파일은 class 이름을 사용하되 첫 글자가 대문자가 되도록한다. 이 방법이 일반적이다.

// Trigger.h
class Trigger
{
	// Something...
};

 

 

9.12 포인터의 작동원리

질문) 연속되어 선언한 변수 주소의 간격이 일정하지 않은 이유

// Char data type Addresses
char a = 'a';
char b = 'b';
char c = 'c';
char d = 'd';

printf("%p\n", &a);
printf("%p\n", &b);
printf("%p\n", &c);
printf("%p\n", &d);


// Char Array Addresses
printf("loop!\n");
char c_list[] = { 'a','b','c','d' };

for (int i = 0; i < 4; ++i)
{
	printf("%p\n", &c_list[i]);
}

 

X64

   0x4B7C98F5C4           0xC4

  -0x4B7C98F5A4  ==   - 0xA4

                                 0x20  = 32byte 

 

X84

  0x00DAF883             0x83

 -0x00DAF877     ==   -0x77

                               0x  C = 12byte 

 

>> 디버깅 기능으로 MSVC가 더 많은 공간을 사용하고있다. [9.16 디버거로 메모리 들여다보기] 참고

https://stackoverflow.com/questions/25442458/why-are-consecutive-int-data-type-variables-located-at-12-bytes-offset-in-visual

 

9.14 포인터와 코딩스타일

int *a, b; // a: int pointer  b: int variable

int* a, b; // a: int pointer  b: int variable

위와 같이 선언했을 때 a만 포인터 자료형이 된다. b는 int 자료형이다.

int *a, *b // a: int pointer  b: int pointer

Asterisk(*)를 꼭 붙여주어야 포인터 자료형이 된다.

 

9.15 NULL 포인터와 런타임 에러

int *a_ptr = NULL;

int a;
scanf("%d", &a);

if(a>10)
  a_ptr = &a;

if(a_ptr != NULL)
  printf("a_ptr is not Null, redirect a_ptr: %d\n", *a_ptr);

NULL 포인터는 주로 포인터 자료형을 사용해도 될 지 확인할 때 쓴다.

 

9.16 디버거로 메모리 들여다보기

 

예)

int a = 1;
int b = 2;
int c = 3;
int d = 4;

printf("%p\n", &a);
printf("%p\n", &b);
printf("%p\n", &c);
printf("%p\n", &d);

 위 printf( ) 함수의 출력값은 아래에 나와있는 값과 같다. 이 때 나온 값은 2자리 씩 묶어서 표기한다.

a값을 키워보자. a = 257일 때 표시되는 값은 01 01이다.

16^1 16^0   16^3 16^2

 0       1        0      1

         16             256

띄어쓰기로 구분되어있는 숫자를 읽을 때는 왼쪽->오른쪽으로 커진다. 띄어쓰기 없이 출력되는 숫자는 순서가 반대로 되어있어서 혼동하기 쉽다.

위 그림에서 INT_MAX와 255를 Hex로 나타내고 있다. INT_MAX를 binary로 나타내면 다음과 같다.

0111 1111 1111 1111 1111 1111 1111 1111

맨 앞에 숫자가 7임을 알 수가 있다. 그래서 마지막 7 f(15)가 나온다.

 

뒤에 있는 cc를 보자. MSVC compiler가 Debug build 기능을 사용한 채로 메모리 주소를 보면 선언한 자료형보다 더 큰 크기의 공간을 사용하고 있다. 위 예제에서 int 자료형이 사용하는 공간은 32bit = 4byte이다. 나머지 뒤에있는 cc cc cc는 무엇일까?

https://en.wikipedia.org/wiki/Magic_number_(programming)#Debug_values 

cc는 초기화되지 않은 stack space를 사용하지 않는지 확인할 때 쓰인다. 

 

9.17 포인터 변수의 크기

x86: 4byte

x64: 8byte

char ch;
float f;
double d;

char* ptr_ch   = &ch;
float* ptr_f   = &f;
double* ptr_d  = &d;

printf("variable size \t char:%zd float:%zd double:%zd\n", sizeof(ch), sizeof(f), sizeof(d));
printf("ptr size \t char:%zd float:%zd double:%zd\n", sizeof(ptr_ch), sizeof(ptr_f), sizeof(ptr_d));
printf("indirected size\t char:%zd float:%zd double:%zd\n", sizeof(&ch), sizeof(&f), sizeof(&d));
printf("type size \t char:%zd float:%zd double:%zd\n", sizeof(char*), sizeof(float*), sizeof(double*));

아래 숫자는 byte를 나타낸다.

X86
X64

 

9.18 포인터형 매개변수

c에선 함수 Parameter에 Ampersand( & ) 사용이 불가능하다.

void swap(int *i, int *j)
{

    printf("i=%d  add=%p\t", *i, i);
    printf("j=%d  add=%p\n", *j, j);

    int temp = *i;
    *i = *j;
    *j = temp;
}

int main()
{
    int a = 10;
    int b = 20;

    printf("a=%d  add=%p\t", a, &a);
    printf("b=%d  add=%p\n", b, &b);
    
    swap(&a,&b);

    printf("a=%d  add=%p\t", a, &a);
    printf("b=%d  add=%p\n", b, &b);
}
Comments