Home C CheatSheet
Post
Cancel

C CheatSheet

1D Array

#1

1
2
3
4
5
int main(void)
{
    int arr[2];
    arr[0]=0, arr[1]=1, arr[2]=2;
}

arr[2]=2;
컴파일러는 배열 접근에 있어서 유효성 검사를 진행하지 않는다.
∴ compile error 발생 X
할당되지 않은 메모리 공간을 침범할 수 있으므로 주의하자.

#2 Length of Array

1
len = sizeof(arr) / sizeof(/* type of element */);

함수 내부에서는 배열의 길이를 구할 수 없다.
함수 내부에서 argument로 받은 배열의 sizeof연산 결과는 64bit 시스템에서는 8byte, 32bit 시스템에서는 4byte이다.
즉, pointer의 크기와 같다.

#3 String & Null (‘\0’)

어떤 문자열 str에 대해 길이가 len이라 하자.
nullstr[len]에 저장되어 있다.

Pointer

#1 Operator & and *

  • &(operand) : operand의 주소 값 반환
  • *(operand) : operand가 가리키는 주소 참조

&연산자는 상수를 피연산자로 사용할 수 없다.

#2

1
2
3
4
5
6
7
#include <stdio.h>

int main(void)
{
    int * ptr1 = 0;
    int * ptr2 = NULL;
}

두 방식 모두 NULL pointer로 초기화 한다.
주소값 0을 갖는 것이 아니라 메모리 아무데도 가리키지 않는다는 의미이다.

Pointer & Array

#1 상수 형태의 pointer

1
2
int arr[3] = {0, 1, 2};
arr = &arr[i]; // for i in [0, 1, 2]

배열 arr는 상수 형태의 pointer 이다.
따라서 arr에 저장되어 있는 주소 값을 변경 할 수 없다.
arr = &arr[i]는 compile error를 발생시킨다.

#2 문자열 상수 & 문자열 변수

1
2
char str1[] = "Hello, World!";
char * str2 = "Hello, World!";
  • str1 : pointer 상수 → 문자열 변수 (문자열 변경 가능)
  • str2 : pointer 변수 → 문자열 상수 (문자열 변경 불가)
1
2
str1[12] = '?'; // 문자열 변경 가능
str2[12] = '?'; // 문자열 변경 불가

위의 코드는 컴파일은 되지만 실행은 안된다.
문제가 발생하는 형태는 컴파일러나 컴파일 모드에 따라서 프로그램이 종료되거나 문제가 있는 코드를 무시하는 등 약간씩 차이가 있다.

#3 Size of Pointer Array

1
2
int * arr1[100];
double * arr2[100];

64bit 시스템 (size of pointer = 8byte) 기준

  • sizeof(arr1) = sizeof(int *) × 100 = 800byte
  • sizeof(arr2) = sizeof(double *) × 100 = 800byte

두 배열의 크기가 같다!

#4 Array of String
문자열 하나를 변수 str에 저장

1
2
3
char * str = "Hello, World!";
// OR
char str[] = "Hello, World!";

문자열 여러개를 문자열 배열 str에 저장

1
char * str[] = {"Hello", "World!"};

큰따옴표로 묶여서 표현되는 문자열은 그 형태에 상관없이 메모리 공간에 저장된 후 그 주소 값이 반환된다.

C에서 문자열을 메모리 주소 값으로 다루기 때문에 문자열 배열 원소의 자료형은 char *이다.

#5 Array of String 2

1
2
3
4
char c = 'c'; // Character
char * str = "Hello, World!"; // String = Pointer
char arrc[] = {'a', 'b', 'c'}; // Array of Character
char * arrs[] = {"Hello", "World!"}; // Array of String = Array of Pointer

Data Types

  • Character : char
  • String : char *
  • Array of Character (≒ String) : collection of char
  • Array of String : collection of char *
  • Array of Characters → There’s no null character.
  • String → There is null character.

Pointer & Function

#1

C에서 함수의 호출 방식은 기본적으로 call-by-value이다.
pointer를 사용해서 call-by-reference 방식으로 호출하는 것을 메모리 주소 값을 argument로 주어 call-by-value로 호출했다고 생각할 수 있다.
그렇기 때문에 pointer를 argument로 주었다고 해서 혹은 parameter가 pointer라고 해서 무조건 call-by-reference가 아니다.
pointer에 대해 call-by-reference가 되게 하려면 double pointer를 사용해야 한다.

#2 배열을 함수의 argument로 전달하는 방법

1
2
void function1(int arr[]);
void function2(int * arr);

함수의 parameter를 선언할 때에 한해서 int * arrint arr[]로 대체할 수 있다.
int가 아닌 다른 자료형도 물론 가능하다.

#3

1
2
3
4
5
void function(int arr[])
{
    int size = sizeof(arr);
    printf("%d\n", size);
}
1
8

64bit 기준으로 8byte이다.

#4 scanf

scanf 함수는 call-by-reference 호출 방식을 사용한다.
argument로 메모리 주소 값을 전달해 주어야 하기 때문에 일반적인 변수의 경우 & 연산자를 사용한다.

#5 const 1

1
2
3
int num = 1;
const int * ptr = &num;
*ptr = 100; // compile error

ptr에 저장된 메모리 주소 값을 참조하여 그 곳에 저장된 값을 변경하는 것을 허용하지 않는다.
다만 ptr을 통해 값을 변경하는 방법만 허용되지 않고 num을 통해 값을 변경하는 것은 가능하다.

#6 const 2

1
2
3
int num1 = 1, num2 = 2;
int * const ptr = &num1;
ptr = &num2; // compile error

ptr을 pointer 상수로 만든다.
int arr[2];에서의 arr과 같은 형태의 pointer 상수이다.
따라서 ptr에 다른 주소 값을 대입하면 compile error가 발생한다.

1
2
int num1 = 1;
const int * const ptr = &num;

위와 같은 선언도 가능하다.

#8 const 3

1
2
3
4
5
void printAllElement(const int * arr, int len)
{
    for(int i=0; i<len; i++)
        printf("%d\n", *(arr + i));
}

const의 사용은 프로그램의 안정성을 높여주는 장점이 있다.

1
2
3
4
5
6
void printAllElement(const int * arr, int len)
{
    int * ptr = arr; // warning message
    for(int i=0; i<len; i++)
        printf("%d\n", *(arr + i));
}

int * ptr = arr;에서 warning message가 발생한다.

Multi-Dimensional Array

#1

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <stdio.h>

int main(void)
{
    int arr[5][5] = {0};

    for (int i=0; i<5; i++) {
        for(int j=0; j<5; j++)
            printf("%d ", arr[i][j]);
        printf("\n");
    }
    return 0;
}
1
2
3
4
5
0 0 0 0 0
0 0 0 0 0
0 0 0 0 0
0 0 0 0 0
0 0 0 0 0

int arr[5][5] = {0};와 같은 방법으로 다차원 배열도 한번에 0으로 초기화할 수 있다.

#2

1
int arr[3][2];

위와 같이 선언된 3 × 2 배열에 대해 메모리는 다음과 같은 순서로 할당된다.

  • &arr[0][0] = 0x7ffee1178b00
  • &arr[0][1] = 0x7ffee1178b04
  • &arr[1][0] = 0x7ffee1178b08
  • &arr[1][1] = 0x7ffee1178b0c
  • &arr[2][0] = 0x7ffee1178b10
  • &arr[2][1] = 0x7ffee1178b14

sizeof(int)의 간격으로 할당되었다.

#3

1
2
3
int arr[3][3] = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}};
int arr[3][3] = {{1}, {4, 5}, {7, 8, 9}};
int arr[3][3] = {1, 2, 3, 4, 5, 6, 7};

모두 가능한 초기화 방식이다.
부족한 부분은 0으로 초기화 된다.

#4

1
2
int arr1[][3] = {1, 2, 3, 4, 5, 6};
int arr2[][2] = {1, 2, 3, 4, 5, 6};

배열의 세로 길이만 생략이 가능하다.

Double Pointer

#1

1
**dtpr == *(*dptr); // true

#2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <stdio.h>

int main(void)
{
    int num1 = 10;
    double num2 = 10.1;

    int * ptr1 = &num1;
    double * ptr2 = &num2;

    int ** dptr1 = &ptr1;
    double ** dptr2 = &ptr2;

    printf("%p %p\n", ptr1, ptr2);
    printf("%p %p\n", dptr1, dptr2);

    return 0;
}
1
2
0x7ffee1984b18 0x7ffee1984b10
0x7ffee1984b08 0x7ffee1984b00

우연히 메모리에 연속적으로 할당되었다.
어찌되었건 pointer의 크기는 8byte이다. (for 64bit system)
pointer의 크기가 8byte임에 착안하여 ptr1ptr2의 주소를 각각 double **, int **형 double pointer 변수에 대입하면 어떻게 될까?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <stdio.h>

int main(void)
{
    int num1 = 10;
    double num2 = 10.1;

    int * ptr1 = &num1;
    double * ptr2 = &num2;

    int ** dptr1 = &ptr1;
    double ** dptr2 = &ptr2;

    double ** dptr11 = &ptr1;
    int ** dptr22 = &ptr2;

    printf("%p %p\n", ptr1, ptr2);
    printf("%p %p\n", dptr1, dptr2);
    printf("%p %p\n", dptr11, dptr22);
    printf("%p %p\n", *dptr11, *dptr22);

    return 0;
}
1
2
3
4
0x7ffee1984b18 0x7ffee1984b10
0x7ffee1984b08 0x7ffee1984b00
0x7ffee1984b08 0x7ffee1984b00
0x7ffee1984b18 0x7ffee1984b10

int *, double * 모두 8byte의 크기이기 때문에 결과가 같게 나오는 것 같다.
그럼 num1, num2의 값을 참조해보면 어떻게 될까?

1
2
printf("%d %f\n", **dptr11, **dptr22); // warning message
printf("%f %d\n", **dptr11, **dptr22);
1
2
858993459 0.000000
0.000000 858993459

두 문장을 추가했다.
물론 num1num2의 값을 올바르게 참조할 수는 없다.
흥미로운 점은 첫번째 줄에서만 warning이 발생했다는 것이다.
어찌보면 강제로 주소 값을 type casting 시켰기 때문에 당연한 결과일 수 있다.
그냥 궁금해서 해봤는데 이런 방법은 쓰지 말자.

#3 Type of Array

1
2
3
4
int arr1[10];       // type of arr1  = int *
double arr2[10];    // type of arr2  = double *
int * arr11[10];    // type of arr11 = int **
double * arr22[10]; // type of arr22 = double **

Pointer & Multi-Dimensional Array

#1

1
2
3
4
5
6
7
8
9
10
11
#include <stdio.h>

int main(void)
{
    int arr[2][3];

    printf("%p\n", arr);
    printf("%p\n", arr[0]);
    printf("%p\n", &arr[0][0]);
    return 0;
}
1
2
3
0x7ffee3a7cb00
0x7ffee3a7cb00
0x7ffee3a7cb00

arr, arr[0], &arr[0][0] 모두 같은 메모리 주소 값을 가리킨다.
arr, arr[0]은 그 자체로 pointer이다.
arr[0][0]은 index (0, 0)의 원소 값을 가리킨다.
index (0, 0)의 원소의 메모리 주소는 &arr[0][0]이다.

#2

1
int arr[2][3];
  • sizeof(arr) = sizeof(int) × 2 × 3 = 24byte
  • sizeof(arr[0]) = sizeof(int) × 3 = 12byte

arrarr[0][0]의 주소를 가리키면서 배열 전체를 의미한다.
arr[0]arr[0][0]의 주소를 가리키면서 index 0행만을 의미한다.

#3 arr + 1

1
2
3
4
5
6
7
int arr1[2][3];
double arr2[2][3];

printf("%p\n", arr1);
printf("%p\n", arr1 + 1);
printf("%p\n", arr2);
printf("%p\n", arr2 + 1);
1
2
3
4
0x7ffee053fb00
0x7ffee053fb0c
0x7ffee053fad0
0x7ffee053fae8
  • arr1 = arr1 + 1 - arr1 = sizeof(int) × 3 = 12byte
  • arr1 = arr2 + 1 - arr2 = sizeof(int) × 3 = 24byte

배열 이름에 1을 더하면 sizeof(/* element type */) × (한 행의 element 개수)byte 만큼 주소값이 더해진다.

#4

1
arr[i][j] == *(*(arr + i) + j) // true

#5 Type of Multi-Dimensional Array

1
element arr[?][num];

arr은 가리키는 대상이 element형 변수이고, pointer 연산 시 (sizeof(element) × num)byte로 증감하는 pointer type이다.

#6 Declaration

1
2
3
4
5
int arr[?][num];
int (*ptr)[num] = arr;

int arr[2][3];
int (*ptr)[3] = arr;

앞에서 언급했듯이 다차원 배열에서 arr[0]도 pointer이다.
arr[0]도 하나의 배열이라고 가정하면, 다음과 같이 생각해볼 수 있겠다.

1
element (*ptr)[/* size of array arr[0] */] = arr;

#7 Array of Pointer v. Pointer to Array

1
2
3
4
int *arr[10]; // Array of Pointer

int arr[?][10];
int (*ptr)[10] = arr; // Pointer to Array
  • Array of Pointer (포인터 배열) : pointer로 이루어진 배열
  • Pointer to Array (배열 포인터) : 배열을 가리키는 pointer = arr, ptr

#8 Argument about Multi-Dimensional Array

1
2
3
4
5
6
void function(int (*arr)[3]);
// OR
void function(int arr[][3]);

int arr[2][3];
function(arr);

#9 *(arr + i) & arr + i

1
2
3
4
int arr[2][3] = {1, 2, 3, 4, 5, 6};

printf("%p %p %p\n", arr + 1, arr[1], &arr[1][0]);
printf("%p %p\n", *(arr + 1), &arr[1]);
1
2
0x7ffee9e2bb0c 0x7ffee9e2bb0c 0x7ffee9e2bb0c
0x7ffee9e2bb0c 0x7ffee9e2bb0c

모두 같은 주소 값을 갖고 있다.
특히 arr + 1*(arr + 1)이 같은 값을 갖고 있다는 것이 놀라웠다.

1
printf("%d\n", *(arr + 1)); // warning message

위의 문장은 warning을 발생시킨다.
또한 warning으로부터 *(arr + 1)의 타입이 int *라는 사실을 알게되었다.

1
printf("%d %d\n", arr[1][2], *((arr + 1) + 2)); // warning message

따라서 두 값은 다른 값으로 출력된다.

그럼 *(arr + 1)arr + 1의 차이가 뭘까?
일부러 warning을 발생시켜 보니 다음을 알 수 있었다!

  • *(arr + 1) : type int *
  • arr + 1       : type int (*)[3]

*(arr + 1)arr + 1은 가리키는 주소 값은 같지만 type이 다르다.
arr + 1arr과 같은 int (*)[3] type이다.
반면에 *(arr + 1)arr[1]과 같은 int * type이다.

#10 arr[i] == *(arr + i)

1
2
int arr[][]; // any 2D array
arr[i] == *(arr + i); // true

앞에서 언급했듯이 다차원 배열에서 arr[i]는 pointer이다.
arr[i]&arr[i][0]와 같은 주소를 가리킨다.

arr[i]가 pointer이므로 arr[i]를 어떤 배열 arri라 하자.
arri는 arr의 i행의 원소들이 순서대로 들어가있다.
따라서 배열 arri의 0번째 원소는 *arri이고 그 값은 arr[i][0]과 같다.

arri의 타입은 int *일 것이다.
따라서 배열 arri의 1번째 원소는 *(arri + 1)이 된다.
또한 이때의 값은 arr[i][1], arri[1]과 같다.

이때 arriarr[i]이고 *(arr + i)와 같다.

여기서 *(arr + i)arr의 원소가 아니다.
*(arr + i)는 주소를 가리키고 int * type이다.

#11

1
2
3
4
5
double * arr1[5];    // 1d array of pointer of double * type
double * arr2[3][5]; // 2d array of pointer of double * type

double **ptr1 = arr1;
double * (*ptr2)[5] = arr2;

Function Pointer & Void Pointer

#1

1
int function(int param);

함수도 프로그램 실행 시에 메모리에 저장되어서 실행된다.
따라서 함수의 이름 function은 함수가 저장된 메모리 주소 값을 의미한다.
이때 function은 배열의 이름과 같이 pointer 상수이다.

#2 Function Pointer 변수

1
2
return_type function(param_type1 param1, param_type2 param2);
return_type (*fptr)(param_type1, param_type2) = function;

function pointer 선언과 초기화 방법이다.

1
2
3
4
5
6
7
double function(int a, int *b);

int a = 10;
int *b = &a;

double (*fptr)(int, int *) = function;
fptr(a, b) == function(a, b) // true

이를 이용하면 함수의 parameter로 function pointer 변수를 사용할 수 있다.

1
2
3
void function2(double (*fptr)(int, int *));

function2(fptr);

#3 Void Pointer

1
void *ptr;

void pointer는 변수의 종류에 상관없이 변수의 주소 값을 저장할 수 있다.
그렇지만 type에 대한 정보가 없으므로 주소 값을 참조할 수 없다.

#4 main Function

1
2
3
int main(void) {}
// OR
int main(int argc, char * argv[]) {}

main함수의 parameter를 위와 같이 할 수도 있다.
argc에는 argument의 개수가 저장된다. (파일 이름 포함)
이는 아마 함수 내부에서 배열 argv의 길이를 구할 수 없기 때문에 main함수 외부에서 계산해서 넘겨주는 것 같다.
argv에는 파일 이름도 포함된다.

Variables

#1 Local Variables

  • 중괄호 내에 선언되는 변수
  • memory의 stack 영역에 할당
  • parameter: local variable

#2 Local Variables

1
2
3
4
5
6
7
8
9
#include <stdio.h>

int main(void)
{
    int i = 100;
    for(int i=0; i<10; i++)
        continue;
    printf("%d\n", i);
}
1
100
1
2
3
4
5
6
7
8
#include <stdio.h>

int main(void)
{
    for(int i=0; i<10; i++)
        continue;
    printf("%d\n", i);
}
1
2
3
error: use of undeclared identifier 'i'
printf("%d\n", i);
               ^
1
2
3
4
5
6
7
8
9
#include <stdio.h>

int main(void)
{
    int i;
    for(i=0; i<10; i++)
        continue;
    printf("%d\n", i);
}
1
10

변수 i의 범위에 유의하자.
또한 for에 의한 반복은 중괄호의 진입과 탈출을 반복하면서 이뤄진다.

#3 Global Variables

  • 중괄호 내에서 선언되지 않는다.
  • 별도의 값으로 초기화 하지 않으면 0으로 초기화된다.
  • memory의 data 영역에 할당
  • 프로그램의 시작과 동시에 할당되어 종료 시까지 존재한다.
  • 같은 이름의 지역 변수가 지역 변수를 가리킨다.

#4 Static Variables

  • static 키워드로 선언할 수 있다.
  • 전역 변수, 지역 변수, 함수에 static 선언을 할 수 있다.
  • 전역 변수와 함수에 static 선언을 하면 외부 파일에서의 접근을 허용하지 않는다.
  • 지역 변수 특성 : 선언된 함수 내에서만 접근이 가능하다.
  • 전역 변수 특성 : 딱 1회 초기화되고 프로그램 종료 시까지 메모리 공간에 존재한다.
  • 전역 변수와 마찬가지로 memory의 data 영역에 할당
  • static 변수는 접근 범위를 제한할 수 있기 때문에 전역 변수 보다 안정적이다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <stdio.h>

void function()
{
    static int var1 = 0; // 접근 범위를 function으로 제한 / 초기화는 처음 1번만, 이후에는 이 줄이 무시되고 메모리에 저장된 값 사용
    int var2 = 0;
    printf("%d %d\n", var1++, var2++);
}

int main(void)
{
    for(int i=0; i<3; i++)
        function();
    return 0;
}
1
2
3
0 0
1 0
2 0

#5 Register Variables

  • Register : CPU 내에 존재하는 크기가 매우 작은 메모리 - Register 변수에 대한 연산이 매우 빠르다.
  • 전역 변수에는 register 선언을 할 수 없다.
  • register 선언을 해도 컴파일러가 합당하지 않다고 판단하면 register에 할당하지 않는다.
  • 반대로 register 선언을 하지 않아도 컴파일러가 필요하다고 판단하면 register에 할당한다.

Reference

This post is licensed under CC BY 4.0 by the author.

Melon Playlist Continuation - Model 2: K Nearest Neighbor

[Waffle] 댓글 조회 REST API 설계하기