비전공자의 C언어 공부기 제 1편


비전공자로 씨언어에 다시 도전한다.

C언어

자료형

  • char : 1
  • short : 2
  • long : 4
  • long long : 8
  • int : 4
  • float : 4
  • doublc : 8
  • (이해를 위한)int* : 4

String이라는 자료형은 없다

데이터

  • “K”
  • 32
  • 32.1
  • “asdf”
  • 주소

c언어 에서는 주소도 데이터다.

포인터1

int a = 20;
int* p = &a;
  • a는 4바이트
  • &a는 a의 시작 주소. 4개의 바이트 주소중 가장 작은 값.
  • a = 20
  • &a = 100 번지
  • p = 100 번지
  • &p = 300 번지
  • 번지라는 주소도 데이터형이다.
  • p도 변수일 뿐이다.
  • p는 주소값 데이터형을 나타내는 변수일 뿐이다.
  • 모든 주소는 포인터다.
  • 번지를 나타내는 주소형앞에 *을 붙이면 주소가 가르키는 값을 뱉어낸다.

(int값이 저장된) 주소를 나타내는 데이터형을 나타내는 자료형이 바로 int* 이다.(int는 예시일뿐)

포인터 2

int a = 20;
int b = 30;
cahr c = 't';
int* p = &a;

퀴즈

  • 햇갈릴 때는 자료형을 비교하라

a = 80; // 가능 왼쪽 자료형은 int, 오른쪽 자료형도 int

p = 20; // 불가능 왼쪽 자료형은 int* 주소값을 저장하는 자료형, 오른쪽은 int

p = &b // 가능 왼쪽 자료형은 int*라는 주소값을 저장하는 자료형, 오른쪽도 int*라는 주소값을 저장하는 자료형이다.

p = &c // 불가능 왼쪽 자료형은 int*라는 주소값을 저장하는 자료형, 오른쪽은 char*라는 주소값을 저장하는 자료형이다.

포인터 3

  • 포인터의 크기 : 자료형과 상관없이 4Byte
  • 왜냐하면 여러가지 자료형의 포인터가 있지만, 포인터는 주소라는 데이터형을 저장하는 것이 핵심이다. 어떤 자료형 포인터든지 각각 데이터형이 저장되는 주소를 저장하는 것이다. int, float, char 데이터형이 저장되는 주소가 따로 나뉘어져있지 않기 때문에. 주소의 범위는 전체 메모리의 크기에 영향을 받는다.메모리가 32비트형 즉 4바이트형인 경우에는 주소의 범위는 42.9억 즉 4바이트로 제한된다. 따라서 포인터가 표현해야 하는 범위는 4바이트면 충분하다. 64비트형 메모리의 경우에는 당연히 주소값이 64비트까지 표시되기 때문에 당연히 포인터가 표현해야하는 크기도 8바이트로 증가한다.
  • 여기서 중요한 의문이 들었다.
  • 그렇다면 크기가 같다면 왜 서로 자료형 포인터들간에 호환이 되지 않는 것일까.
p = &b // 가능 - 왼쪽 자료형은 int* 주소값을 저장하는 자료형, 오른쪽도 int*라는 주소값을 저장하는 자료형이다.
p = &c // 불가능 - 왼쪽 자료형은 int* 주소값을 저장하는 자료형, 오른쪽은 char*라는 주소값을 저장하는 자료형이다. 

위의 코드가 왜 불가능한 것일지 생각해보았다. 결국 주소값만 나타내는 거라면 int* 타입과 double* 타입이 달라야하는 이유가 있을 거라고 생각했다. 잠시 고민한 후에 이유를 생각해냈다.

  • int* 형 주소는 double*형 주소와의 차이점은 *(int*) *(double*)을 떠올리면 좋을 것같다. 포인터는 데이터의 시작 주소을 저장하고 있다고 알고 있지만 신기하게도 *만 붙이면 정확한 데이터를 가지고 온다.
  • 데이터를 가져오기 위해서는 데이터의 시작 주소 뿐만 아니라 끝나는 주소도 알아야한다.
  • 하지만 int와 double, char, 문자열 등은 하나의 타입을 구성하는 각각 주소 길이가 다르다.(문자형 배열을 말하는 것이 아니라 int - 4byte, char - 4byte etc)
  • 온전한 데이터를 가져오기 위해서 Int형은 4개의 주소를 double형은 8개의 바이트 주소를 불러와야 한다.
  • 하지만 문자 배열을 가르키는 char* 포인터라고 해도 당연히 char 배열의 길이까지는 저장하지 않는다.

즉 각각의 자료형 포인터들은 컴퓨터에게 인트형은 4개의 주소까지 불러와야 한다고, 더블형은 8개의 주소까지 불러와야한다고 알려주어야한다. 즉 컴퓨터가 값을 구분하려면 각각 자료형이 달라야 한다는 말이 이해가 갔다.

포인터 4

  • 여기까지 왔다면 한가지 밝힐 사실이 있다.
  • 사실 C에서 엄밀히 말하면 int*라는 자료형은 없다. C언어에서 int *p;라는 표현방식 때문에 이해하기가 어려워지는 경우를 경험했기 때문에 나는 int* 자료형이 있다고 생각하는 게 훨씬 이해하기 좋다고 생각한다. 그래서 앞으로도 int* p;라는 코드를 계속 사용할 것이다. 그렇지만 이번 파트에서는 int*를 자료형이라고 이해했을 때 실수하게 되는 사례를 정리하겠다.
int *p; 
int * p;
int* p;  
// 위의 세 코드는 같다. 
int *p, a,b ; // p만 주소를 나타내는 int형 포인터이고 a, b는 정수를 나타내는 인티져다.
int* p, a ,b; // 여기에서 int*를 인티져 포인터 자료형이라고 이해했을 때 실수하기 좋은데, 세가지 변수가 모두 주소를 나타내는 것이 아니라, 위의 코드와 같은 역할을 하는 것이다. 

Void Pointer

  • 포인터는 주소값을 저장한다.
  • 또한 타입 을 저장해서 어디까지 읽어와야 할지를 기억한다.
  • 하지만 타입에 상관없이 주소값만 가지는 포인터가 있다.
  • 바로 void 포인터.
  • 어떤 자료형이든 가르킬 수 있다는 장점
  • 하지만 *pointer를 실행시켜도 포인터는 어디까지가 원하는 데이터인지 모르기 때문에 가져올 수 없다.

주소

  • 주소는 16진수로 저장.
  • 주소의 단위는 1바이트이다.
  • 주소를 프린트 할 때는 %p를 사용하는 게 정석(16진수로 표기하기 위해서)
  • 주소를 프린트 할 때 %u를 써서 표기하면 10진수로 나온다.

*의 3 가지 용법

  1. 4 * 3 곱셈
  2. int* p; 변수 선언
  3. *p = 30 p가 참조하는 변수에 대입

메모리

총 3개의 영역이 있다.

  • 스택 : 지역변수가 저장된다. 함수안에서 쓰이는 변수들이 여기에 저장된다.
  • 힙 : 메모리 직접 할당하는 경우이다.
  • 데이터 : 전역변수와 함수 코드가 저장된다. 여기에서는 세부적으로 코드가 저장되는 영역, 데이터영역으로 구분할 수 있겠다.

배열과 포인터

  • 배열과 포인터는 공통점과 차이점이 있다.
  • 둘 사이의 관계를 명확하게 파악하는 게 어쩌면 씨언어의 핵심 중 하나일 수도 있다.

  • 배열명은 변수가 아니다. 배열명은 시작 주소를 갖는 상수이다.
  • 문자열은 선언시 메모리(데이터) 영역에 단 한 번만 저장된다.
  • [문제] char a[10] = "abc"를 이해못해 혼자 끙끙 알았다. 연산자 = 기준으로 왼쪽은 char 타입이고 오른쪽은 “abc” 상수가 선언된 주소값 즉 char형 주소 타입을 나타내고 있다.
  • 양쪽의 타입이 다르기 때문에 불가능한 선언문이라고 생각해 혼동하고 있었다. 하지만 이는 내가 대입과 초기화를 같은 것이라고 생각해서 그런 것이다. 지금 읽고 있는 책에서 C++ 창시자 비잔 스트로스트룹은 대입과 초기화는 엄밀히 다른 과정이라고 말하고 있는 게 떠올랐다.
  • 위에서 배열을 선언하고 초기화하는 과정은 대입하는 것과 다른 과정임을 이해한다면, 배열a[10]이라고 선언하고 이 공간에 string “abc”를 카피하는 것까지가 초기화 과정이라고 받아들이기로 했다. 누군가가 설명해준 것이 아니라 부 정확할 수 있지만 지금 내가 떠올린 생각중엔 지금까지 배운 로직과 맞는다.
int* a[5] = {2, 4, 6, 8, 10};
int* p = a;
a = 50; // 가능할까? 불가능하다. 

  • a의 값은 100번지라고 하자.
  • a라는 배열명은 시작 주소를 갖는 상수이기 때문에 int형 값인 50을 대입하는 것이 불가능.
  • 이 시작주소는 절대 변할 수 없다. 그래서 strcpy 함수를 사용할 때도 시작주소는 변하지 않고 그 안의 원소만 바뀌게 된다.
  • 중요! 배열명이 주소를 가르키는 상수라면, 포인터는 주소를 가르키는 변수.
  • a + 1은 a가 가르키는 주소값에 a원소의 자료형 크기만큼 더한다. int 자료형은 4바이트이기 떄문에 104라는 주소값을 가르킨다.
  • 마찬가지로 p도 100을 가르키고 p + 1 은 104라는 주소값을 가르킨다.
  • 따라서 *(a)과 a[0], *(p)모두 2라는 int 값이고 *(a + 1)과 a[1], *(p+1) 모두 4라는 int 값이다.
  • 심지어 p[0], p[1]도 같은 값을 나타낸다.
  • 사실 컴퓨터는 배열을 모른다. 우리가 배열이라고 표현하는 것은 컴퓨터에게 없는 개념이다. 컴퓨터에게는 주소값과 자료형과 각 자료형의 개수로 파악하기 때문에 이 3가지만 만족한다면 포인터와 배열은 컴퓨터에게 같은 것이기 때문이다. 주소값을 나타내는 것이 상수이든 변수이든 컴퓨터가 값을 가져오는 데에서는 상관이 없다.
  • 흐.. 씨언어 포인터에 대해서 이해도가 점점 올라가고 있다.

차이점

sizeof(a) // 20 byte ( 4byte *   5)
sizeof(p) // 4 byte

  • 배열명은 주소값을 나타내지만 동시에 배열의 사이즈가 얼마인지 기억하고 있어야 한다.
  • 반면에 포인터는 배열의 시작 주소만 가르킬뿐 배열의 사이즈는 알 수 없다.

포인터 5

  • 포인터 간 뺄셈
int* v[10];
int* a = v;
int* b = v + 2;

prinft("주소값의 차이 %d", v - v);

위 코드의 출력값은 어떻게 될까요.

    1. 답은 2일 것이다.
    1. 답은 int의 사이즈인 4 바이트 * 2인 8이 될 것이다.
  • 답은 2입니다. 같은 타입의 포인터 간의 뺄셈은 자동으로 자료형의 크기를 계산해 두 포인터 주소값 사이에 몇 개의 자료가 있는지만을 반환합니다.

배열과 함수

void Disp(int * x, int y);
int main()
{
	int a[5] = {0, 1, 2, 3, 4, 5};
	n = sizeof(a) / sizeof(int) ;
	Disp(a, n)	
}
  • 배열이 함수의 인수로 전달될 때 배열이 복사되는 게 아니다
  • 시작 주소만 넘겨준다.
  • 항상 배열개수도 함께 넘겨야 한다.
  • 함수가 배열을 인자로 받을 때 아래의 표현은 모두 같은 방식이다.
  • int* x
  • int *x
  • int x[]
  • int x[5]

const

  • const는 본인 바로 왼쪽에 쓰인 자료형을 상수로 제한한다. 단 맨 앞에서 쓰여 왼쪽에 자료형이 없는 경우에는 바로 오른쪽에 있는 자료형을 제한한다.
  • ex
const char* p;
char* const p;
  1. const char* p : const가 맨 앞에 있으므로 const는 바로 오른쪽에 있는 char라는 자료형을 제한. 즉 pointer가 가리키는 변수의 내용을 바꿀 수는 없다. 그러나 다른 char를 가르키는 pointer로 수정할 수 있다.

  2. char * const p: const는 왼쪽에는 *가 있다. 즉 포인터를 제한하는 const. 이 경우 p는 같은 문자 메모리를 가르키는 상수 pointer이다. p의 내용은 수정이 가능하나, 다른 메모리를 가르키는 포인터로 고칠 수는 없다.

#

문자 배열과 문자형 포인터

  • “song” 라는 문자열의 자료형은 char*(주소) 이다.
char string[10] = "apple";
char* b = "banana";

// 2개의 코드는 근본적으로 다르다.

string = "pear"; // 불가능. string은 주소값을 가진 상수이고 "pear"는 주소값이 정해진 상수이기 때문에  상수에 새로운 상수를 대입할 수 없다. 
strcpy(string, "pear") // 가능

b = "pear"; // 가능 "pear"은 메모리의 데이터 영역에 저장되어 있는 상수이다. b는 char* 즉 주소값이고 "pear"또한 char* 이기때문에 변수 b에 "pear"를 넣을 수 있다.
strcpy(b, "pear"); // 불가능 . b는 "apple"를 가르키고 있고 "apple"는 데이터영역의 상수이기 때문에 값을 바꿀 수 없다.  

포인터 배열과 배열 포인터

이름만 들어도 뭐가 뭔지 햇갈리는 데요.

  • 포인터 배열은 배열이다.
  • 배열 포인터는 포인터
  • 이 두가지를 기억하는 것이 시작입니다.
  • 두 가지 타입모두 포인터 속성을 가지고 있는데요. 사실 선언을 할 때부터 햇갈리게 됩니다.

배열 포인터

int a[3][5];
int (*p)[5];

p = a;

배열 포인터는 배열을 가르키는 포인터입니다.

  • int (*p)[5] 에서 5라는 숫자는 포인터는 인트형 포인터이지만 한번에 5 * sizeof(int), 즉 20바이트 만큼씩 넘어가고 싶다는 말입니다. 그래서 배열을쉽게 가르킬 수 있게끔요.
  • 배열 포인터를 쓰지 않고 일반 포인터를 쓰게 되면 p에 더해지는 + 1당 4바이트씩만 이동할테니 p + 1은 a[1]의 주소값이 아닌 a[0][1]의 주소값을 가르키게 되겠죠?

포인터 배열

int a = 10;
int b = 20;
int c = 30;

int* ptr[3];
ptr[0] = a;
ptr[1] = b;
ptr[2] = c;

포인터를 한번에 여러개를 선언하고 싶을 때 사용하는 게 포인터 배열입니다.

  • 여러 포인터를 하나의 배열에 담고, 각각의 포인터마다 다른 변수 주소를 가르키게 할 수 있습니다.

제가 틀린 퀴즈

int x = 1;
while(x--)
{
	printf("123\n");

}
// 123은 프린트 될까요?
        
  • while문의 괄호 안에 x–는 x가 계산된다음 –가 적용되더라도 while(0)이 적용되어 123이 프린트 되지 않을 줄 알았는데 아니었습니다.
  • while문에 1이 적용되어 들어간 이후에 1이 0으로 바뀌는 것이었습니다.~

call by value

call by reference

  • 주소값을 넘겨주는 것과 변수 자체를 넘겨주는 일은 다르다
  • call by reference는 무엇을 넘겨주는 가.
  • 이중 포인터는 변수 자체를 념겨주는 것과 같은가.

#궁금증

  • int형 20과 같은 숫자는 메모리에 이진수로 저장될 때 어떻게 저장될지 대충 감이오는데, ‘a’ ‘ㅠ’ ‘나’같은 char는 어떻게 저장될까.
  • 예상 : 문자를 표현할 때 숫자를 이진으로 표현하는 방식과 다른 약속이 있을 것이고, 저장위치가 다른 건 아닐 것이고.. 숫자와 구분하기 위한 패턴도 이미 숫자가 바이트를 최대로 활용해서 수를 표현하기 때문에 넣지 못할 것같은데 어떻게 구분해서 넣을까. 혹시 숫자를 비워두고 그 부분을 문자를 입력하는 데 쓸까? 근데 그럼 숫자와 어떻게 구분하지.





© 2017. by yunsu

Powered by dolphin