1103-TIL(5 Productivity Strategies from the Mind of Richard Feynman,C언어)

그로밋·2023년 11월 4일
0

krafton jungle

목록 보기
21/58
post-thumbnail

5 strategies

1. Don’t worry about what others are thinking

“You have no responsibility to live up to what other people think you ought to accomplish. I have no responsibility to be like they expect me to be. It's their mistake, not my failing.”

Work in your own way and don’t let other people’s criticisms delay you.

2. Don’t think about what you want to be, but what you want to do

If you do what you want to do, everything else will fall into place. If you undertake tasks you want to do first, your enjoyment will increase your productivity and enhance your focus.

3. Stop trying to be a know-it-all

Embrace your ignorance and let it propel you to new, interesting discoveries. Try to prove yourself wrong and don’t be afraid to fail.

4. Have a sense of humor and talk honestly

Don’t pretend to be better than others and don’t fool yourself into thinking you have all the answers. Like Feynman, be humble and talk directly and honesty.

4번이 제일 내게 필요한 말인듯 하다. 남보다 나은 척 하려 하지 말고 답을 다 안다고 생각하지 말라. 겸손하되 돌려말하지 말고 정직해라. 원문링크

C

동적 메모리

동적 할당이 필요한 또 다른 경우는 임시적인 메모리가 필요할 때이다. 예를 들어 텍스트 파일에서 특정 문자열이 있는지만 알고 싶다고 하자. 텍스트 파일을 검색하려면 일단 이 파일을 읽어야 하고 그러기 위해서는 텍스트 파일을 읽기 위한 버퍼가 필요하다. 이런 버퍼를 미리 정적 할당해 놓을 필요없이 파일 크기만큼만 동적 할당한 후 원하는 작업만 하고 해제하면 된다.

char *buf=동적할당(파일크기만큼)

buf에 파일 읽음

원하는 작업 - buf에 문자열이 있는지 조사

buf 해제

이 파일 크기가 최대 1M라고 할 때 char buf[1048576]과 같이 전역 버퍼를 정적 할당해 놓고 작업하는 것도 가능하기는 하다. 임시 기억 공간인 스택은 용량이 그다지 크지 않기 때문에 buf는 지역변수로 선언할 수 없으며 전역으로만 선언할 수 있다. 하지만 이렇게 하면 이 프로그램이 항상 1M를 더 쓰게 되므로 시스템의 전반적인 효율이 떨어지게 될 것이다. 뿐만 아니라 텍스트 파일의 크기가 1M 이하여야 한다는 규칙은 어디에도 존재하지 않는다. 동적 할당이란 필요할 때 필요한만큼만 메모리를 할당해 사용하고 다 쓰면 버리는 것이다.

동적 할당된 메모리는 이름이 없는 변수라고 할 수 있다. 독점적인 메모리 영역을 차지하고 있으므로 일단 값을 기억할 수 있지만 이름이 없으므로 오로지 포인터로만 접근할 수 있다. 그래서 malloc 함수가 리턴하는 포인터는 반드시 적절한 타입의 포인터 변수로 대입받아야 한다. 시작 번지를 잃어버리면 할당된 메모리를 쓸 수 없음은 물론이고 다 사용하고 난 후에 해제하지도 못한다.

메모리 관리 원칙

  1. 메모리 관리의 주체는 운영체제이다.
    응용 프로그램은 직접 메모리를 관리할 수 없으며 메모리가 필요할 경우 운영체제에게 할당 요청을 해야 한다. 16비트 운영체제에서는 응용 프로그램이 임의의 주소 공간을 액세스할 수 있었지만 32비트의 보호된 운영체제들은 안전성을 높이기 위해 응용 프로그램이 임의의 메모리를 액세스하는 것을 금지하고 있다. 반드시 운영체제를 통해서만 메모리를 할당받을 수 있다.

  2. 운영체제는 메모리가 있는 한은 할당 요청을 거절하지 않는다.
    메모리라는 것은 결국 작업을 위해 존재하는 것이므로 응용 프로그램 (=이 프로그램을 쓰는 사용자)이 달라고 하는만큼 내 주도록 되어 있다. 만약 요청한 양만큼 메모리가 남아 있지 않을 경우는 에러를 리턴하여 응용 프로그램에게 메모리가 없다는 것을 알려준다. 최근의 운영체제들은 요청한 만큼 메모리가 남아 있지 않을 경우 가상 메모리 공간을 늘려서라도 필요한 메모리를 만들어 줄 정도로 친절하다.

  3. 한 번 할당된 메모리 공간은 절대로 다른 목적을 위해 재할당되지 않는다.
    운영체제는 메모리 공간을 누가 얼마만큼 사용하고 있는지 모두 기억하고 있으며 반납하기 전에는 응용 프로그램이 이 공간을 독점적으로 사용할 수 있도록 보장한다. 그래서 한 번 할당한 메모리는 일부러 해제하지 않는 한은 언제까지든 안심하고 사용할 수 있다.

  4. 응용 프로그램이 할당된 메모리를 해제하면 운영체제는 이 공간을 빈 영역으로 인식하고 다른 목적을 위해 사용할 수 있도록 한다. 즉, 특정 메모리 공간을 동시에 두 프로그램이 사용할 수는 없지만 순서대로 번갈아 가면서 사용하는 것은 가능하다. 메모리 공간이 무한하지 않기 때문에 응용 프로그램들은 자신이 꼭 필요한만큼만 할당해서 사용하고 다 쓴 후에는 반드시 반납해서 다른 목적에 사용될 수 있도록 해야 한다.

할당 및 해제

메모리를 동적으로 할당 및 해제할 때는 다음 두 함수를 사용한다.

void *malloc(size_t size );
void free(void *memblock );

10바이트가 필요하면 malloc(10)이라고 호출하고 1000바이트가 필요하면 malloc(1000)이라고 호출하면 된다. 실행중에 할당하는 것이므로 malloc(Num)과 같이 변수도 사용할 수 있다. malloc은 응용 프로그램이 필요로하는 양만큼 운영체제에게 할당을 요청하며 운영체제는 사용되지 않는 빈 영역(힙)을 찾아 요청한만큼 메모리를 할당하여 그 시작 번지를 리턴한다. 응용 프로그램이 할당한 메모리를 어떤 목적에 사용할지는 알 수 없으므로 malloc은 void *형을 리턴하며 받는 쪽에서는 원하는 타입으로 캐스팅해야 한다.

free 함수는 동적으로 할당한 메모리를 해제한다. 응용 프로그램은 메모리를 다 사용한 후에 반드시 free 함수를 호출하여 메모리를 해제해야 한다. 그래야 이 영역이 다른 프로그램을 위해 재활용될 수 있다. 다음 코드는 정수형 변수 10개를 담을 수 있는 메모리를 할당하는 예이다.

int *ar;
ar=(int *)malloc(10*sizeof(int));
// ar 사용
free(ar);

동적으로 할당된 메모리를 사용하려면 그 시작 번지를 기억해야 하므로 포인터 변수가 필요하다.
이 경우 정수형 변수를 위한 메모리를 할당하는 것이므로 정수형 포인터 ar을 선언했다. malloc을 호출할 때는 필요한 메모리양을 바이트 단위로 전달하는데 정수형 변수 10개를 담을 수 있는 메모리의 총 크기는 10*sizeof(int), 즉 40바이트여야 한다. int가 항상 4바이트라는 보장이 없으므로 여기에도 반드시 sizeof 연산자로 크기를 계산해야 한다.

malloc은 인수로 전달된 크기만큼의 메모리를 할당하고 그 시작 번지를 리턴하되 리턴 타입이 void 형이므로 이 포인터를 변수에 대입할 때는 반드시 원하는 타입으로 캐스팅할 필요가 있다. 물론 void 형 변수로 받을 수도 있는데 이렇게 하면 받을 때는 편하지만 쓸 때마다 캐스팅해야 하므로 더 불편하다. 정수형 포인터에 대입해야 하므로 (int *)로 캐스팅했다. 이렇게 할당하면 ar 번지 이후 40바이트를 응용 프로그램이 배타적으로 소유하게 된다.

즉 포인터 ar이 가리키는 번지는 마치 ar[10] 정수형 배열과 같아지며 메모리내에서의 실제 모양과 용도, 적용되는 문법도 배열과 동일하다. ar 번지 이후의 40바이트는 다른 목적에 사용되지 않으므로 마치 정적 할당된 배열처럼 이 메모리를 활용할 수 있다. 물론 다 사용하고 난 후에는 반드시 free 함수로 메모리를 해제해야 한다. 그럼 동적 할당의 실제 예를 보도록 하자.

#include <Turboc.h>
void main()
{
     int *arScore;
     int i,stNum;
     int sum;
     
     printf("학생수를 입력하세요 : ");
     scanf("%d",&stNum);
     
     arScore=(int *)malloc(stNum*sizeof(int));
     
     if (arScore == NULL) {
          printf("메모리가 부족합니다.\n");
          exit(0);
     }
     for (i=0;i<stNum;i++) {
          printf("%d번 학생의 성적을 입력하세요 : ",i+1);
          scanf("%d",&arScore[i]);
     }
     sum=0;
     for (i=0;i<stNum;i++) {
          sum+=arScore[i];
     }
     printf("\n총점은 %d점이고 평균은 %d점입니다.\n",
          sum,sum/stNum);
     free(arScore);
}

malloc 함수는 할당에 실패하면 에러의 표시로 NULL을 리턴하며 그래서 이 함수를 호출할 때는 위 예제처럼 malloc이 리턴한 번지를 반드시 점검하는 것이 원칙이다. 메모리가 부족한 상황은 언제든지 발생할 수 있고 만약 이 점검을 하지 않으면 0번지를 액세스할 위험이 있다. 제대로 만든 프로그램은 어떠한 극한 상황에서도 최소한 죽지는 말아야 한다.그러나 32비트 환경은 메모리가 충분할 뿐만 아니라 운영체제의 메모리 관리 기법이 정교해져서 작은 메모리를 할당할 때는 에러 점검을 생략해도 큰 무리가 없다. 얼마 정도가 작은지에 대한 명확한 기준은 없지만 일반적으로 메가 단위 이상을 할당할 때는 꼭 점검해야 하며 수십~수백 바이트 정도는 굳이 점검하지 않아도 상관없다.

재할당

다음 함수(씨얼록이라고 읽는다)는 malloc 함수와 마찬가지로 메모리를 할당하되 필요한 메모리양을 지정하는 방법만 다르다.

void *calloc( size_t num, size_t size );

첫 번째 인수 num은 할당할 요소의 개수이고 size는 요소의 크기이다. malloc은 필요한 메모리를 바이트 단위 하나로만 전달받지만 calloc은 두 개의 값으로 나누어 전달받는다는 점이 다르다. malloc이 "몇 바이트 할당해 주세요"라고 요청하는 것에 비해 calloc은 "몇 바이트짜리 몇 개 할당해 주세요"라고 요청하는 것이다. 그래서 다음 두 호출문은 동일하다.

ar=(int )malloc(10sizeof(int));
ar=(int *)calloc(10,sizeof(int));

구조체같은 큰 데이터의 배열을 할당할 때는 calloc으로 할당하는 것이 더 보기에 좋고 코드를 읽기에도 좋다.
calloc이 malloc과 또 다른 차이점은 메모리 할당 후 전부 0으로 초기화한다는 것이다. malloc은 메모리 할당만 하므로 할당된 메모리에는 쓰레기값이 들어 있지만 calloc으로 할당하면 할당 후 모든 메모리를 0으로 채운다. 할당 후에 배열을 바로 초기화해야 한다면 malloc 호출 후 memset을 사용할 수도 있지만 이 방법보다는 calloc 함수로 할당하는 것이 더 편리하다.

다음 함수는 이미 할당된 메모리의 크기를 바꾸어 재할당한다.

void realloc( void memblock, size_t size );

최초 할당한 크기보다 더 큰 메모리가 필요할 때는 이 함수로 크기를 조정할 수 있다. 원래 크기보다 더 작게 축소 재할당하는 것도 가능하기는 하지만 보통은 확대 재할당하는 경우가 많다.

linkedlist

singly linkedlist

노드 맨 앞 삽입

typedef struct node
{
	int value;
    struct node* next;
}node;

node *head = NULL;  //head는 null
void insert_node_front()
{
    node* newNode; // node를 가르키는 newNode라는 포인터 변수						
    newNode = (node*)malloc(sizeof(node));//malloc으로 node를 생성후 
    								//newNode라는 포인터 변수가 가르키게
	scanf("%d", &newNode->value); // 
    newNode->next=NULL; // 다음 노드가 없으니까 널
    if(head==NULL){ // head가 널이라면
    	head = newNode; // 첫 노드의 주소를 헤드에
        return; // 함수종료
        //현재까지상황: head도, newNode도 새로 생성된 주소를 가르키고 있는 상황
    }
    // 두번째 노드가 생성 되었을 때
    newNode->next=head; //newNode의next가 3번주소값을 가지게.(3번주소를 가르키게) 
     head = newNode; // head가 새로 생성된 4번 주소의 노드를 가르켜서 4번 노드가 첫노드가 될 수 있게
}

https://jeongorithm.tistory.com/11

newNode = (node*)malloc(sizeof(node));

  • 여기서 malloc으로 메모리 공간을 만들때 왜 newNode라는 포인터에 넣는가?
    -> 생성된 메모리는 주소를 리턴하기 때문
  • 왜 형변환을 (node*)로 하는가?
    -> malloc으로 할당된 주소가 노드타입의 주소라서 형변환을 노드 타입의 포인터로 해야한다.

두번째 노드가 생성 되었을 때를 생각해보자.

  1. newNode = (node*)malloc(sizeof(node)); 이 줄 때문에, newNode 포인터가 새로 생성된 노드의 주소를 저장하게 된다.(가르키게 된다.)

  2. newNode->next=NULL; 이 줄 때문에, 새로운 노드에 새로 입력받은 값이 들어가고 next포인터는 null 인 상황.

  3. if(head==null) 에서 head 봤더니 head에는 3번 노드의 주소가 들어가 있다. 즉, 첫번째 노드는 연결이 되있다는 얘기. 그렇다면 처음 들어온 3번노드 앞에 두번째로 들어온 4번노드가 붙어야하기 때문에 4번노드가 3번노드를 가르키게 해야한다. 즉, 4번 노드의 next를 3번 노드의 주소를 줘야한다.
    -> (newNode->next=head;)

newNode->next=head;

  • head로 하는 이유: head가 3번 노드의 주소를 가지고 있기 때문.
  1. 이제 4번 노드가 첫노드가 되어야 되기 때문에 head에는 4번 노드의 주소값(newNode가 항상 새로운 노드의 주소 값을 알고 있기 때문에 이를 이용한다)이 저장을 해줘야 한다.

즉, 새로운 노드가 생성되면 연결 리스트의 맨 앞에 추가가 되는 형태이고, 아래의 두번의 스텝으로 생성된 순서와 역순으로 데이터가 저장된다.
newNode->next=head;
head = newNode;

profile
Work as though your strength were limitless. <S. Bernhardt>

0개의 댓글