Introduction
Why do we use '#include <stdio.h>'?
stdio.h 안에 미리 정의되어 있는 함수를 사용하기 위해 사용합니다.
How are C strings represented?
char *ptr = "ABC"
아래의 예는 문자열을 초기화하는 다른 일반적인 방법들입니다.
char *str = "ABC"; char str[] = "ABC"; char str[]={'A','B','C','\0'};
How do you declare a pointer?
Pointer는 메모리의 주소를 의미합니다. 포인터 타입은 컴파일러에게 얼마나 많은 바이트를 읽거나 쓸 필요가 알려줄 수 있어 유용합니다. 포인터는 아래와 같은 방식으로 선언할 수 있습니다.
int *ptr1; char *ptr2;
C의 문법에 따라 int*나 어떤 포인터는 실제 그 타입이 아닙니다. 각각의 포인터 변수 앞에 asterisk(*)을 붙일 필요가 있습니다. 일반적으로 다음과 같은 실수를 저지르기 쉽습니다.
int* ptr3, ptr4;
위와 같은 선언은 ptr3만 포인터로 선언되고, ptr4는 int형이 됩니다.
int *ptr3, *ptr4;
이와 같이 선언해야 ptr3 와 ptr4 모두 포인터형으로 선언됩니다.
How do you use a pointer to read/write some memory?
*ptr = 0; // Writes some memory.
이러한 선언으로 C가 할일은 pointer의 타입을 알고(여기서는 int) sizeof(int) 바이트만큼 0을 쓰는 것입니다. 즉, ptr이 0x1000이라고 했을 때, 0x1000, 0x1001, 0x1002, 0x1003 네 바이트는 모두 0이 됩니다.이렇게 쓰여지는 바이트의 수는 포인터 변수의 타입에 의존합니다. 이와 같은 동작은 구조체를 제외한 모든 기본 타입에 유효합니다.
What is pointer arithmetic?
char *ptr = "Hello"; // ptr holds the memory location of 'H' ptr += 2; //ptr now points to the first'l'
하지만 만약 4바이트인 int라면 ptr에 1을 더함으로서 4바이트 뒤의 주소값은 가리킵니다.(int가 4바이트 자료형이기 때문에)
char *ptr = "ABCDEFGH"; int *bna = (int *) ptr; bna +=1; // Would cause iterate by one integer space (i.e 4 bytes on some systems) ptr = (char *) bna; printf("%s", ptr); return 0;
위 코드는 어떤 값을 반환할까요?
맨 처음 ptr은 "ABCDEFGH" 문자열의 시작하는 주소값, 즉, 'A'가 있는 주소값을 가리키고 있었습니다.
하지만 bna라는 int pointer 변수에 이 주소값을 int pointer로 강제 형변환을 시켜 대입했습니다.
그 다음 이 bna라는 포인터에 1을 더하면, 'A'로부터 4바이트 떨어진 'E'를 가리키게 됩니다.
여기서부터 문자열의 끝까지 printf를 통해 출력하면 "EFGH"라는 문자열이 출력될 것입니다.
하지만 ptr 포인터에 1을 더해 포인터연산을 수행하면 1바이트 뒤인 'B'를 가리키므로 출력하면 "BCDEFGH"가 될 것입니다.
C포인터 연산은 항상 자동적으로 포인터가 가리키는 타입의 사이즈만큼 조절되어 연산되므로 void pointer에 대한 포인터 연산은 불가능합니다.
int *ptr1 = ...; int *offset = ptr1 + 4;
위와 같은 수행은
int *ptr1 = ...; char *temp_ptr1 = (char*) ptr1; int *offset = (int*)(temp_ptr1 + sizeof(int)*4);
이와 같은 과정을 통해 값을 얻는다고 생각해야 합니다.
위의 코드에서 offset은 ptr에 4를 더해 4*4(sizeof(int))인 16바이트가 증가했습니다.
이를 아래에 코드에서는 char*로 변환해 16을 증가시키는 것을 보여주고 있습니다.
포인터 연산을 할 때는 잘 생각한 다음에 얼마나 바이트를 이동시켜야 하는지 잘 확인해야 합니다.
What is a void pointer?
void *give_me_space = malloc(10); char *string = give_me_space;
위와 같은 예에서는 따로 형 변환이 필요 없습니다. C에서 void*를 적절한 자료형으로 변환시켜주기 때문입니다.
NOTE
gcc와 clang은 전부 ISO-C호환이 아닙니다. 그래서 void pointer에 연산을 허용할 수 도 있습니다. 이러한 연산은 char pointer처럼 취급되어 연산되지만, 모든 컴파일러와 호환되는 것이 아니므로 이러한 연산을 하지 않도록 합시다.
Does printf call write or does write call printf?
How do you print out pointer values? integers? string?
int num1 = 10; printf("%d", num1); //prints num1
pointer의 경우
int *ptr = (int *) malloc(sizeof(int)); *ptr = 10; printf("%p\n", ptr); //prints the address pointed to by the pointer printf("%p\n", &ptr); /*prints the address of pointer -- extremely useful when dealing with double pointers*/ printf("%d", *ptr); //prints the integer content of ptr return 0;
string의 경우
char *str = (char *) malloc(256 * sizeof(char)); strcpy(str, "Hello there!"); printf("%p\n", str); // print the address in the heap printf("%s", str); return 0;
위와 같습니다.
How would you make standard out be saved to a file?
./program > output.txt
#To read the contents of the file,
cat output.txt
위와 같이 실행하면 program이 실행되면서 출력된 output stream이 output.txt에 저장되게 됩니다.
조금 더 복잡한 방법은 close(1)을 이용해 standard output이 file descriptor를 닫고 output.txt를 열어 사용하면 가능합니다.
(0 : standard in, 1 : standard out, 2: standard error)
What's the difference between a pointer and an array?
Give an example of something you can do with on but not the other
char ary[] = "Hello"; char *ptr = "Hello";
array의 이름은 그 array이 첫 바이트를 가리킵니다.
array와 ptr은 모두 아래와 같은 방법으로 출력이 가능합니다.
char ary[] = "Hello"; char *ptr = "Hello"; // Print out address and contents printf("%p : %s\n", ary, ary); printf("%p : %s\n", ptr, ptr);
하지만 array는 변형이 가능해 그 안에 내용을 바꿀 수 있습니다. 하지만 정해진 array의 길이를 벗어나지 않도록 주의해야 합니다.
아래에서 사용할 World는 Hello보다 길지 않기 때문에 별다른 문제가 발생하지는 않습니다.
위와 같은 보기에서 ptr은 오직 읽기만 가능한 메모리를 가리키고 있기 때문에(string literal이 static하게 저장되어 있음), 그 내용을 바꿀 수는 없습니다.
strcpy(ary, "World"); // OK strcpy(ptr, "World"); // NOT OK - Segmentation fault (crashes)
따라서 위와 같이 strcpy를 통해 ptr의 주소의 값을 바꾸려고 하면 segmentation falut가 발생하게 됩니다.
ptr이 가리키는 곳의 내용을 직접 바꾸는 것은 불가능하지만 ptr이 다른 곳을 가리키게 하는 것은 가능합니다.
ptr = "World"; // OK! ptr = ary; // OK! ary = (..anything..) ; // WONT COMPILE // ary is doomed to always refer to the original array. printf("%p : %s\n", ptr, ptr); strcpy(ptr, "World"); // OK because now ptr is pointing to mutable memory (the array)
맨 첫번째 줄은 Hello의 시작 주소값을 가리키던 ptr을 World문자열이 시작하는 주소값으로 가리키는 곳을 바꾸는 것이기 때문에 가능합니다.
두번째 줄에서는 ptr이 가리키는 주소값이 array인 ary의 시작 주소값이 됩니다. 이때의 ptr은 array의 주소값을 가키는 것이므로 변형이 가능합니다.
즉 strcpy를 통해 메모리에 접근해 그 내용을 바꾸는 것이 가능합니다.
array의 포인터는 오직 stack의 메모리값만 가리킬 수 있는 것에 반해 char* 포인터는 메모리의 어느 부분이든 가리킬 수 있습니다. 대부분의 일반적인 경우에서 포인터는 대부분 힙 영역의 메모리를 가리킬 것입니다. 이 때 heap 영역의 메모리는 수정될 수 있는 부분입니다.
sizeof() returns the number of bytes. So using above code, what is sizeof(ary) and sizeof(ptr)?
char str1[] = "will be 11"; char* str2 = "will be 8"; sizeof(str1) //11 because it is an array sizeof(str2) //8 because it is a pointer
위에서 sizeof(str2)는 null character를 포함해 10이 되어야 할 것 같지만 char*의 크기인 8이 됩니다.
string의 길이를 알 때 sizeof를 사용하는 것은 주의해야 합니다!
Which of the following code is incorrect or correct and why?
int* f1(int *p) {
*p = 42;
return p;
} // This code is correct;
char* f2() {
char p[] = "Hello";
return p;
} // Incorrect!
이러한 일을 방지하려면 아래와 같이 작성해야 합니다.
char* f3() { char *p = "Hello"; return p; } // OK
이처럼 선언하면 pointer p는 "Hello"라는 string constant의 주소값을 가리킵니다. 이 주소값을 return해주므로 f3가 리턴된 이후도 p의 값은 변하지 않는다.
char* f4() { static char p[] = "Hello"; return p; } // OK
또는 위와 같이 선언한다면 static 변수는 프로세스의 lifetime동안 존재하므로 유효하다(static variable은 heap이나 stack에 존재하지 않는다)
How do you look up information C library calls and system calls?
How do you allocate memory on the heap?
int *space = malloc(sizeof(int) * 10);
What's wrong with this string copy code?
void mystrcpy(char*dest, char* src) { // void means no return value while( *src ) { dest = src; src ++; dest++; } }
위 코드에는 두 가지 문제점이 있습니다. 첫 번째는 주소의 값을 복사하는 것이 아니라 dest가 가리키는 값을 src를 가리키게 바꾸고 있습니다.
또한 string의 마지막이 항상 null byte인데 이를 복사하지 않고 있습니다.
while( *src ) { *dest = *src; src ++; dest++; }
*dest = *src;
이와 같은 코드를 짜면 주소를 바꾸는 게 아닌 그 주소가 가리키는 값을 복사해올 수 있고 마지막에 null byte까지 복사해올수 있습니다.
마지막으로 아래와 같은 코드는 대입 후 while비교가 일어나므로 while문 안에서 null byte를 복사하는 것까지 완료할 수 있습니다.
while( (*dest++ = *src++ )) {};
How do you write a strdup replacement?
// Use strlen+1 to find the zero byte... char* mystrdup(char*source) { char *p = (char *) malloc ( strlen(source)+1 ); strcpy(p,source); return p; }
위와 같은 코드를 이용하면 문자열을 복사하는 함수를 만들 수 있습니다.
How do you unallocate memory on the heap?
int *n = (int *) malloc(sizeof(int)); *n = 10; //Do some work free(n);
What is double free error? How can you avoid?
What is a dangling pointer? How do you avoid?
int *p = malloc(sizeof(int)); free(p); *p = 123; // Oops! - Dangling pointer! Writing to memory we don't own anymore free(p); // Oops! - Double free!
위에 예에서 p는 malloc후 바로 free되었는데, 아래에서 p에 접근해 123이라는 값을 썼습니다. 하지만 이 메모리는 더이상 allocate된 곳이 아니기 때문에 다른 곳에 쓰일 수 있으므로 엉뚱한 값이 나올 수 있습니다. 그 이후에 이미 free했던 p를 다시 한번 free함으로써 error가 발생하게 됩니다.
이러한 일을 방지하기 위해선 우선 올바른 프로그램을 짜야합니다.
또한 free된 메모리를 reset하여 다시 접근할 수 없도록 해야 합니다.
p = NULL; // Now you can't use this pointer by mistake
위와 같이 free된 메모리를 NULL로 초기화시켜 준다면 p에 접근해서 사용하려고 할 때 프로그램 충돌이 일어나게 할 것입니다.
What is an example of buffer overflow?
What is 'typedef' and how do you use it?
typedef float real; real gravity = 10; // Also typedef gives us an abstraction over the underlying type used. // In the future, we only need to change this typedef if we // wanted our physics library to use doubles instead of floats. typedef struct link link_t; //With structs, include the keyword 'struct' as part of the original types
위 코드에서 맨 처음에 real 이라는 이름의 자료형을 float로 정의했습니다.
그래서 그 다음 라인에서 real gravity는 float 자료형이 될 것입니다. 또한 이 한 줄만을 바꿔줌으로써 그 다음에 사용되는 real이라는 자료형을 double로도, int로도 한번에 바꿀 수 있습니다.
아래에서 구조체 자료형인인 struct link를 link_t라는 이름으로 정의하고 있습니다.
typedef function 또한 사용할 수 있습니다. 다음은 typedef function의 예입니다.
typedef int (*comparator)(void*,void*); int greater_than(void* a, void* b){ return a > b; } comparator gt = greater_than;
위에서는 function pointer를 사용하고 있습니다.
function pointer는 일반적으로
int (*gt)(void *, void*)와 같은 형식으로 함수 포인터 변수 gt를 선언할 수 있습니다.
gt = greater_than과 같이 사용해
선언된 함수 포인터 gt에 greater_than을 지정하면, gt(a,b)와 같은 형태로 greater_than(a,b)를 호출할 수 있습니다. 이러한 함수 포인터를 좀 더 편하게 사용하기 위해 typedef를 사용하고 있습니다.
typedef int (*comparator)(void*,void*);
위에서는 comparator라는 타입이 (void*, void*)를 인자로 받아 int를 리턴해주는 함수 포인터라는 것을 선언합니다.
이렇게 선언된 후에는 comparator gt와 같은 형식으로 함수포인터를 쉽게 선언할 수 있습니다. 이 함수 포인터가 가리킬 함수를 지정해주고 쉽게 사용이 가능합니다. 이러한 함수 포인터는 분기에 따라 다른 함수를 호출해야 하는 경우가 많이 생길 경우 유용합니다.
'Angrave System Programming > Learning C' 카테고리의 다른 글
Common Gotchas(3) (0) | 2019.01.09 |
---|---|
Common Gotchas(2) (0) | 2019.01.09 |
Common Gotchas(1) (0) | 2019.01.09 |
Text Input And Output(2) (0) | 2019.01.08 |
Text Input And Output(1) (0) | 2019.01.08 |