[C언어] 데이터 영역(Data Area), 스택 영역(Stack Area), 힙 영역(Heap Area)

데이터 영역(Data Area)


전역변수, static 변수.


프로그램 시작과 동시에 할당.

프로그램 종료되어야만 메모리에서 소멸.

전역변수가 데이터 영역에 올라가는 일은 main 함수의 호출보다 먼저 진행된다.





스택 영역(Stack Area)


지역변수, 매개변수.




힙 영역(Heap Area)


프로그래머가 관리하는 메모리 영역.

배열의 크기 선언을 반드시 상수로 사용해야 하는 이유



스택, 데이터 영역에 할당될 메모리의 크기는 런타임(run-time)이 아닌 컴파일되는 동안(컴파일 타임)에 결정되어야 한다.



힙에 메모리를 할당하는 것을 두고 '동적 할당(Dynamic Allocation)'이라 한다.

반대로 스택과 데이터 영역에 메모리를 할당하는것 '정적 할당(Static Allocation)이라 한다.




malloc 함수 & free 함수.


malloc 함수.

동적으로 메모리를 할당하는 함수이다. = 힙 영역에 메모리를 할당하는 함수.

할당한 메모리의 주소(첫번째 바이트의 주소)를 리턴한다.

만약에 메모리 부족으로 인해서 메모리 할당에 실패했다면, NULL 포인터가 리턴되므로, 리턴된 포인터가 NULL인지 아닌지 확인해야한다.










[C언어] strlen, strcpy, strncpy, strcat, strncat, strcmp, strncmp

1. strlen 함수


문자열의 길이를 반환해 주는 함수이다. NULL 문자는 문자열 길이에 포함되지 않는다.




2. strcpy 함수


문자열을 복사. BUT. 저장할 버퍼가 입력받은 문자열 길이보다 작으면 오버플로우 발생.

따라서 strncpy함수를 선호한다.


3. strncpy 함수


최대 길이 n을 넘지 않고 문자열을 복사한다.


strncpy로 NULL문자를 뺀 길이까지만 복사하고, 맨 뒤에 '\0'(NULL) 을 넣어준다.



4. strcat 함수, strncat 함수

기존에 존재하는 문자열의 뒤에다가 다른 문자열을 추가하는(덧붙이는) 함수이다.
역시나 strcat은  문자열이 저장될 배열의 공간이 추가하는 문자열의 길이에 비해 충분하지 못한 경우 오버플로우 발생.

strncat함수를 쓰자.



5. strcmp 함수, strncmp함수


문자열의 길이를 비교하는 것 X. 문자열 내용 자체를 비교 O.




위에 예제는 str1 == str2 (배열 이름 = 포인터) 이므로 포인터를 비교한 것임.

 



strcmp 함수예제


strncmp함수는 처음부터 n까지 비교하는 함수이다.



[C언어] 표준 입 출력과 버퍼(Buffer)

표준 입 출력 함수를 사용하는 경우에는 버퍼라는 것을 제공받게 된다는 특징이 있다.


버퍼 = 여분의 임시 메모리 공간.


키보드를 통해서 입력하는 데이터는 실행중인 프로그램으로 바로 읽혀지는 것이 아니라, 일단은 입력 버퍼라는 곳에 저장이 된다는 것을 알 수 있다.

그리고 나서 입력 버퍼에 존재하는 데이터가 프로그램으로 이동하는 것이다.



프로그램상에서 printf와 같은 함수 호출을 통해서 모니터에 데이터를 출력하려는 경우에도 바로 출력이 이루어지는 것이 아니라, 일단 출력 버퍼라는 곳에 저장이 되었다가 출력 스트림을 통해서 모니터로 전송된다.




버퍼링(Bufferint)을 하는 이유는?


버퍼링이란 데이터를 전송하는데 있어서 목적지로 바로 보내는 것이 아니라, 중간에 버퍼(여분의 임시 저장소)를 둬서 전송하고자 하는 데이터를 임시 저장해 두는 것을 말한다.


임시 저장된 데이터가 어느정도 채워지면 한꺼번에 전송한다. -> 성능 향상을 위해서임.



fflush 함수 ?


버퍼를 비우는 작업을 하는 함수.


fflush 함수의 인자로 전달되는 스트림의 버퍼를 비워버린다.


stdin 과 같은 입력 스트림이 전달되면 입력 버퍼 안에 존재하는 데이터들은 모두 지워진다.


stdout과 같은 출력 스트림이 전달되면 출력 버퍼 안에 존재하는 데이터들은 즉시 출력된다.


 

 fgets함수는 표준 입, 출력 함수이므로, 함수 호출 시 키보드를 통해서 입력받은 문자들은 일단 입력 버퍼에 저장이 된다. 
그 다음 실행중인 프로그램으로 문자열이 전송되는데, 입력받은 문자가 fgets 함수에서 요구하는 7문자보다 많기 때문에 입력 버퍼에 있는 데이터는 한번에 읽혀지지 않는다. 

즉 읽혀지고 남은 문자들이 여전히 버퍼에 존재하게 된다. 

 그 다음 두 번째 fgets함수가 호출될 때는 입력 버퍼에 여전히 문자가 남아 있기 때문에 키보드로부터 문자열을 입력받지 않고, 버퍼에 남아 있는 문자들을 읽어들이게 된다. 따라서 우리가 원하는 실행결과를 얻지 못하게 된다.


[C언어] puts,fputs, gets, fgets,

문자열을 출력할때 쓰는 puts, fputs 함수.


puts 함수는 자동으로 줄바꿔줌.

fputs 함수는 아님. 그래서 fputs("\n", stdout); 로 한줄 띄우기.



문자열 입력 할 때 쓰는 gets, fgets 함수.


fgets를 쓰도록 하자. 왜냐하면. gets를 쓰면미리 할당해 놓은 배열보다 큰 길이의 문자열을 입력받으면 오버플로우 발생.


fgets(str, sizeof(str), stdin);



 



문자열을 12345678999 넣으면
123456789 까지만 출력됨.

fgets함수는 입력받을 수 있는 최대 문자열의 길이 n을 초과하는 문자열이 입력되는 경우에는
n-1까지의 문자만 입력 받고, 마지막에 NULL 문자를 삽입해준다.



[C언어] 포인터, const 키워드

const 키워드를 포인터 앞에 붙이면  포인터를 상수화 한다.


포인터 자체를 상수화.

포인터가 가리키는 변수를 상수화.


이 두가지 의미가 있다.



1. 포인터가 가리키는 변수의 상수화.


int a = 10;

const int* p = &a;

*p = 30 ; //안됨.

a = 30;   //가능.


포인터 p가 가리키는 변수의 값을 못 바꾸게 하겠다는 의미.

a자체가 상수화 되는 것이 아니고, 포인터 p를 통해서 변수 a값을 변경하는 것을 막는다.


2. 포인터 상수화


int a = 10;

int b = 20;

int* const p = &a;

p = &b;  // 오류

*p = 30; //된다.


포인터 p가 지니는 주소 값을 변경 할 수 없다는 뜻이다.




따라서, 포인터도 상수화 시키고, 포인터가 가리키는 데이터도 상수화하려면?


const int* const p = &a;



void print(const int* ptr)
{

int *p = ptr;    //에러.

}


ptr은 const 키워드에 의해서 상수화 되어있다.

이는 print 함수 내에서 ptr이 가리키는 변수의 조작을 허용 않겠다는 의미.

따라서, int* 로의 대입이 허용되지 않는다.



[C언어] 문자열 상수, 포인터 배열 함수

문자열 상수


char str1[5] = "abcd";    //문자열 변수

char *str2 = "ABCD";     //문자열 상수


배열 str1은 문자열 전체를 저장.

포인터 str2는 메모리상에 저장되어 있는 문자열 상수 "ABCD"를 단순히 가리키고만 있음.


문자열 상수는 메모리 공간이 할당되면, 문자열 상수의 주소 값을 반환한다.


문자열을 입력받을땐


scanf("%s", str) 로 str앞에 &를 붙이지 않는다.

왜냐하면은 배열 이름 str은 배열의 주소를 나타내므로, & 연산을 할 필요가 없기 때문이다.





int ArrAdder(int* pArr, int n);


int main(void)
{

int arr[10] = ...;

int sumOfArray;


sumOfArray = ArrAdder(arr, sizeof(arr) / sizeof(int) );

}



sizeof(arr) / sizeof(int) ==> 배열 요소의 개수.



int arr[5 ] ==> sizeof(arr) ==> 20 출력됨

int* pArr = arr; ==> sizeof(pArr) ==> 포인터 이기에 int 형인 4 출력됨.


일반적으로 배열을 인자로 전달받는 함수의 경우, 대부분 배열의 길이도 함께 전달받도록 구현해야한다!



int function(int pArr[]){ }

int function(int* pArr){ }


선언 int pArr[] 와 int *pArr은 완전히 같은 것이다.


다만 인자를 배열로 전달하는 것을 좀 더 명확하게 할 수 있도록 int pArr[] 선언을 허용하는 것이다. 즉, 함수의 매개 변수로 선언한때만 가능!


int main(void)
{

int pArr[] ;//안된다! 함수의 매개 변수로 선언한때만 가능!

}


하지만 매개 변수 선언시에도 int *arr와 같은 선언을 주로 사용하자.


[C언어] 재귀적 함수 호출, n!(Factorial 계산)

재귀적 함수 호출


함수 내에서 자기 자신을 다시 호출하는 형태이다.


재귀적 함수 호출은 부담스럽지만 어렵고 복잡한 문제를 쉽게 해결 할 수 있다.

자료구조와 알고리즘에서는 빠질 수 없는 개념.


n! 팩토리얼 계산.


#include <stdio.h>

int f(int n);


int main(void)
{

int val, result;


printf("정수 입력 : ");

scanf("%d", &val);


if(val < 0)
{

printf("0 이상을 입력해야 합니다. \n");

return 1;

}


result = f(val);    //팩토리얼 계산.

printf("%d!의 계산결과 : %d \n", val, result);

return 0;

}


int f(int n)
{

if(n == 0) //n이 0이면 1을 반환
    return 1;

else      // 아니면 n* (n-1)을 반환.

return n*f(n-1);

}

[C언어] static 변수, register 변수

static 변수


특성이 전역변수와 비슷하다.

메모리 공간을 할당받고 초기화가 이루어지면, 프로그램이 종료될 때까지 소멸되지도 않고 초기화도 두 번 다시 일어나지 않는다.

단, 지역내에서만 접근을 허용한다.


void Add(int a, int b)
{

static int val = 0;

val +=a+b;

}


static 변수는 한번만 초기화 되므로, static int val =0 ; 이것은 딱 한번만 실행된다. 두번째 Add 함수 호출 부터는 실행되지 않는다.


프로그램이 실행되는 동안에 계속해서 유지되어야 한다.

그러나, 접근 영역이 한정되어 특정 지역에서만 사용한다면 전역변수를 쓰지말고 static 변수를 사용한다.



register 변수


int a;

register int b; //레지스터 변수 b 선언.


CPU의 '레지스터'라는 메모리 영역에 저장된다. 레지스터는 CPU의 접근이 가장 빠른 메모리 공간이다. 따라서 변수 a보다 b가 빠르게 처리된다.


그러나 regsiter 변수의 선언은 명령이 아니라 요청에 가깝다. 왜냐하면 register라는 선언은 무시될 수도 있기 때문이다.

CPU의 레지스터는 그 크기가 제한되어 있는 메모리 공간이다. 따라서 이 공간에 변수를 할당하는 것이 여의치 않을 경우 이러한 요구는 충분히 무시될 수 있다.


생성과 소멸이 빈번한 변수를 register 변수로 선언하게 되면 많은 성능의 향상을 기대할 수 있다.

그러나 register 변수의 선언과 관련해서 고민할 필요는 없다. 대부분 컴파일러가 알아서 붙여준다.





[C언어] printf, scanf, %e, %E

printf


printf 이름의 맨 끝 f는 formatted(서식화된)을 의미한다. 즉, 출력의 양식을 스스로 결정지어 출력한다는 의미이다.


%e, %E


3.1245e+2 or 2.45e-4


'부동소수점 표현 방식' 이라는 것이며, 흔히 공학용 수지 계산에서 많이 사용되는 방법이다.



double d1 = 1.23e-3; //0.00123

double d2 = 1.23e-4; //0.000123

double d3 = 1.23e-5; //0.0000123


출력되는 필드 폭 지정하기.


%4d  => 필드 폭을 4칸 확보하고, 오른쪽 정렬. /    값/

%-4d => 필드 폭을 4칸 확보하고, 왼쪽 정렬.   /값    /



scanf


실수를 입력받기 위해서는 서식 문자 %f, %le (scanf에 사용할땐 %e가 아니다.) 를 사용.


[C언어] 리터럴(literal) 상수, 심볼릭(Symbolic) 상수

리터럴(literal) 상수


덧셈이라는 연산은 CPU에 의해서 진행이 된다.

CPU는 메인메모리상(RAM)에 존재하는 데이터들만 조작(연산)할 수 있다.


int val = 30 +40


30, 40이라는 정수가 메모리 공간에 상수로서 저장이 된다.

30, 40 처럼 변수와 달리 이름이 없는 상수를 가리켜 '리터럴(literal) 상수'라고 한다.



심볼릭(Symbolic) 상수 


이름을 지니는 상수로 표현하는 방법에는 const 키워드를 이용하는 방식과 매크로를 이용하는 방식 2가지가 있다.


const 키워드 + 자료명 상수


선언과 동시에 초기화 해야만 한다.


int형을 char형으로 바꿀때 일어나는 현상.

char형은 1바이트. int형은 4바이트이다. 이때 char형으로 대입하기 위해 '상위 비트의 손실'이 발생한다.

상위 3바이트를 단순히 버려버린다.