#c

C 언어 포인터

c 에서 포인터를 통해 직접 데이터의 주소에 접근할 수 있으며, 이러한 특성 때문에 C 언어가 low 하다고 불린다.

1
2
3
4
5
6
int main(void)
{
int num =5; // 정수형 변수 num을 생성한다.
int * pnum = # // 정수형 변수 num을 가리키는 포인터 변수 pnum을 선언한다.
*pnum = 10; // pnum이 가리키는 변수에 10을 저장하라
}

이처럼 특정 자료를 가리키는 자료형을 정의 할 수 있고, 이를 포인터라 한다.

포인터는 특정 자료형을 가리키고 있다.

여기서 포인터는 어떤 변수를 가리키는 그 자체이므로 메모리의 특정 주소값을 가진다.

때문에 포인터를 선언할 때에는 매우 신중해야 하는데 선언 후 아무 값도 할당 하지 않는다면 포인터는 쓰레기 값으로 초기화 되기 때문이다.

1
int *pnum = 399;

가령 위와 같은 코드는 매우 위험한데 이는 pnum이라는 포인터에 399를 할당하고 있고, 우리는 399 의 주소값이 얼마나 중요한 주소인지 전혀 이해하고 있지 못하기 때문이다. 이를 해결하기 위해 반드시 우리는 해당 포인터 값이 이상한 값을 가리키지 않도록 해야 하는데 주로 다음과 같이 널 포인터를 지정해 준다.

1
2
int *ptr2 = 0;
int *ptr2 = NULL;

위와 같이 NULL 혹은 0 으로 초기화 하면 시스템은 이것을 0 번째 주소값으로 인식하지 않고 아무것도 가리키지 않는 포인터를 만들어 낸다.

포인터와 배열의 관계

c 언어에서 배열의 이름은 그 자체로 포인터 이며, 정확히 말하면 배열의 첫번째 요소의 주소값이다.

1
2
3
int arr[3] = {1, 2, 3};
printf("%p", arr); // 주소값이 나온다.
printf("%p", arr[0]); // 위와 같은 주소값이 나온다. 즉, 배열의 주소는 첫번째 값의 주소를 의미한다.

변수 형태의 문자열 vs 상수형태의 문자열

1
2
3
4
5
char str1[] = "my string";
char * str2 = "your string";

str1[0] = "x";// 문자열 변경 성공
str2[0] = "x"; // 문자열 변경 실패

위처럼 모든 문자열 혹은 배열을 선언하면 그 자체는 그 문자열 혹은 배열의 첫번째 요소의 주소값이 된다.

하지만 위에서 처럼 문자열 혹은 배열 자체를 선언하는 경우와 그것의 주소값을 선언하는 경우에 따라 그 값의 변경 가능 유무가 결정이 된다.

위의 예처럼 str1의 경우 문자열 자체를 저장한 변수형 문자열이며, str2의 경우 문자열에 대한 주소값을 저장하는 상수 형태의 문자열이기 때문에 가리키는 값의 내용이 변경될 수 없다.

포인터 변수로 이루어진 배열

다음과 같이 포인터 변수로 이루어진 배열을 선언할 수 있다.

1
2
char * strArr[3] = {"Simple", "String", "Array"};
printf("%s \n",strArr[0]) // Simple

위처럼 “” 로 감싸진 문자열은 그 자체로 주소값을 나타내기 때문에 위와 같은 결과가 나오게 된다.

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

자료형이란 무엇인가요?

자료형이란 데이터를 표현하는 기준이 되며, 정수형 자료형, 혹은 실수형 자료형 인지 등의 데이터의 표현 기준을 정해주고, 명확한 데이터의 크기를 알려줌으로써 메모리에 보다 효과적으로 자료가 저장될 수 있도록 해줍니다.

대표적인 자료형으로는 char, int, float, double 등이 있으며 각각 1바이트, 4바이트, 4바이트, 8바이트의 크기를 가집니다.

이러한 자료형의 크기를 구하는 연산자로는 sizeof() 함수가 있으며 다음과 같이 사용합니다.

1
2
int num = 1
numSize = sizeof(num)

우리가 컴퓨터에서 표현하는 자료는 앞서 말했듯이 정수와 실수의 큰 기준으로 나뉘게 되며, 여기서 실수형을 표현하는 경우 정확도 향상을 위해 float 이 아닌 double을 사용 합니다.

또 어떤 자료형들이 있나요?

C 프로그래밍 시에 자료형 앞에 unsigned와 같은 키워드가 붙은 것을 많이 보았을 겁니다.
이 것은 바로 해당 자료형이 unsigned로 나타내는 것을 의미하며 즉, 음수를 구분하지는 못하지만 2배 더 큰 숫자까지 나타낼 수 있게 해주는 것을 의미합니다.

그밖에, 문자를 표현하기 위한 char 자료형이 있는데 이는 ASCII 코드에 의거하여 문자를 나타내는 것을 의미합니다.
이러한 ASCII 코드는 1바이트 즉 256가지의 문자를 표현하며, 기본적으로 컴퓨터 내에서 정수형 자료형으로 나타내어 지므로, 정수형 표현과 문자형 표현이 모두 가능합니다.

가령 다음의 예제를 살펴봅시다.

1
2
3
char examplChar = "a"

printf("%d", examplChar)

이 경우 문자 a의 정수형 표현을 ASCII 코드표에서 찾아 나타내어 줍니다.

상수란 무엇인가요?

상수는 int, double에 근거한 것으로 메모리 내에 이름이 없는 literal 상수와 이름이 있는 symbolic 상수로 구분됩니다.
이러한 상수는 한번 선언되면 그 값이 바뀔 수 없으며, 만약 이름이 있는 symbolic 상수로 선언되는 경우 이름은 대문자와 아래바 _ 로 구성되는 것이 일반적입니다.

다음의 예는 literal 상수의 예를 보여줍니다.

1
int exampleNum = 5 + 6

이렇게 literal 상수는 별도의 선언이 없이 값을 나타내며, 위의 경우 5와 6은 literal 상수입니다.

다음은 symbolic 상수의 예를 나타냅니다.

1
const int MAGIC_NUMBER = 1

이렇게 기본 자료형 int 앞에 const 키워드를 붙여 줌으로써 symbolic 상수를 선언하며, 선언과 동시에 초기화가 되어야 합니다.

만약 상수에 자료형을 지정해 주고 싶다면 어떻게 할까요?

다음은 상수 자료형에 float 으로 표기를 지정해 주는 예를 나타냅니다.

1
long exampleNum = 5.1235L

이처럼 상수의 뒤에 U, L, UL, LL, F 등을 나타냄으로써 자료형을 알려줄 수 있습니다.

자료형 형 변환

이렇게 한번 선언된 자료형은 프로그래밍 도중에 변경될 수 있으며 이것을 형 변환 이라고 합니다.

형변환 에는 자동으로 형이 변환되는 자동 형 변환 과 사용자가 형을 변환해 주는 강제 형 변환 이 있습니다.

먼저 자동 형 변환은 보통 자료형을 일치시켜 계산해야 하는 경우 또는 대입 시에 강제로 형 변환이 되는 경우, 연산 시 빠른 연산을 위해 CPU 에서 자동으로 변환이 되는 경우가 있습니다.

먼저 자료형 일치를 위한 자동 형 변환의 예를 살펴봅시다.

1
2
3
int num1 = 5
double num2 = 5.6235
double result = num1 + num2

위의 예의 경우 num1 과 num2 는 다른 자료형을 가졌기 때문에, 덧셈연산이 불가능 합니다.
이렇게 되면 컴퓨터는 자동으로 num1 과 num2 중 하나의 자료형에 맞추어 연산을 진행하게 되는데, 그 판단의 근거는 정확도를 높이는 방향 으로 진행된다는 것 입니다.

위 예의 경우 num1 과 num2 가 int 형으로 변환되어 계산이 된다면 큰 오차가 생기므로 적은 오차범위를 가지도록 둘 다 double 형으로 변환이 되어 계산이 되게 됩니다.

다음 예는 대입의 과정에서 자동 형 변환이 일어나는 경우입니다.

1
2
int num1 = 129
char result = num1

num1 은 int 형으로 선언이 되었지만 char 자료형에 할당됨에 따라 4바이트에서 1바이트로 그 크기가 줄어들게 됩니다.
이 경우에는 최상위 바이트의 손실이 일어나게 되며 이 경우 00000000 00000000 00000000 10000001 에서 10000001 로 바뀌게 되어 부호가 바뀌게 되는 문제가 생깁니다.

다음은 강제 형 변환 의 예를 살펴봅시다.

1
2
3
4
5
int num1 = 5
int num2 = 10
double result

result = (double)num1/num2

위 예는 num1과 num2의 연산을 통해 얻은 0.5를 double로 강제 형 변환 시켜주어 result에 할당하는 예를 보여줍니다.
이처럼 (자료형) 키워드를 앞에 붙여줌으로써 강제로 해당 자료형을 바꾸어 줄 수 있습니다.


스트림과 데이터의 이동

컴퓨터 프로그래밍에서 입력이란 프로그램을 기준으로 프로그램 안으로 데이터가 흘러들어가는 것이고 출력이란 프로그램을 기준으로 데이터가 나오는 것이다.

우리는 흔히 컴퓨터 프로그램에 입력을 입력하고 문자열이 출력되는 것을 보면서 어떻게 컴퓨터가 모니터에 저런 문자열을 출력할 수 있는지 궁금해 한다.

우리가 입력하는 문자열이 어떻게 모니터에 출력되는 것일까?

우리가 작성하는 프로그램과 모니터는 기본적으로 연결되어 있는 것이 아닌 떨어져 있는 개체이며 둘 사이에 무언가 정보를 주고 받기 위해서는 이들을 연결시켜주는 다리가 필요하다. 이러한 매개물이 바로 스트림 이다.

c 언어 내의 printf 혹은 scarf 와 같은 함수는 운영체제에게 스트림의 형성을 요구하는 것이며 이는 표준 입출력 라이브러리인 stdin, stdout 을 통해 이루어 진다.

단일 문자의 입출력

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <stdio.h>
int main(void)
{
int ch1, ch2;

ch1 = getchar(); // 표준 입력 스트림으로부터 하나의 문자를 입력 받아서 반환
ch2 = fgetc(stdin); // 문자를 입력받을 스트림을 지정

putchar(ch1); // 인자로 전달된 문자정보를 stdout으로 표현되는 표준 출력 스트림으로 전송하는 함수
fputc(ch2, stdout); // 문자를 전송할 스트림을 지정할 수 있다. 가령 파일을 대상으로 데이터를 전송한다.

return 0;
}

문자열의 입출력

문자열의 출력

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

int main(void)
{
char *str = "Simple String";

printf("1, puts test -------- \n ");
puts(str);
puts("So Simple String");

printf("2. fputs test ------ \n");
fputs(str, stdout); // 두번째 인자에서 출력의 대상 선택
printf("\n");
fputs("So Simple String", stdout);
printf("\n");

printf("3. end of main --- \n");
return 0;
}

문자열의 입력

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

int main(void)
{
char str[7];
int i;

for (i = 0; i < 3; i++)
{
fgets(str, sizeof(str), stdin); // str의 사이즈 만큼 문자열을 입력받는다.
printf("Read %d: %s \n", i + 1, str);
}

return 0;
}

표준 입출력과 버퍼

위처럼 표준 입출력 함수를 통해 데이터를 입출력 하는 경우 해당 데이터들은 운영체제가 제공하는 메모리 버퍼 를 중간에 통과하게 된다. 이 메모리 버퍼는 데이터를 임시로 모아두는 메모리 공간이다.

컴퓨터는 왜 이러한 입출력 스트림을 바로 저장하지 않고 입 출력 버퍼에 보관후에 저장하는 것일까?

이는 바로 데이터의 효율성 때문이다.

모니터와 키보드라는 외부 자원을 활용하는 것은 컴퓨터에게는 상당히 무거운 태스크 이며 이를 최소한으로 수행하기 위해 주고 받는 데이터를 모은 다음 한번에 처리하는 것이다.

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×