C Language Basic

C Language [핵심정리] - 17

cCcode 2021. 5. 25. 22:14

우선 들어가기에 앞서 제가 그전에 언급했던 코드를 보여드리겠습니다.

* 동적할당을 통해 포인터를 2차원배열처럼 사용하는 예제

#include <stdio.h>
#include <stdlib.h>

void assign_value(int** numPtr, int row, int col);
void print_value(int** numPtr, int row, int col);

int main()
{
	int row, col;
	printf("	세로 : ");
	scanf_s("%d", &row);
	printf("	가로 : ");
	scanf_s("%d", &col);


	int** numPtr = malloc(sizeof(int*) * row);

	if (row < 2)
	{
		if (numPtr != NULL)
			for (int i = 0; i < row; i++)
				numPtr[i] = malloc(sizeof(int) * col);
	}
	else return -1;

	assign_value(numPtr, row, col);
	print_value(numPtr, row, col);

	if (row < 2)
	{
		if (numPtr != NULL)
			for (int i = 0; i < row; i++)
				free(numPtr[i]);
	}
	else return -1;

	free(numPtr);
	return 0;
}

void assign_value(int** numPtr, int row, int col)
{
	for (int i = 0; i < row; i++)
	{
		for (int j = 0; j < col; j++)
			numPtr[i][j] = i + j;
	}
}

void print_value(int** numPtr, int row, int col)
{
	printf("\n	세로가 %d 이고, 가로가 %d 인 2차원 배열\n\n", row, col);
	for (int i = 0; i < row; i++)
	{
		printf("		");
		for (int j = 0; j < col; j++)
			printf("	%d ", numPtr[i][j]);
		printf("\n");
	}
}

결과

제가 그 해당 워닝을 발생하지 않게 하려고보니 값을 제한적으로 입력하게 되었습니다. 제가 생각해도 미숙하네요..;; 

 

1. 문자열 사용하기

C언어에서는 문자 자료형char은 있지만 문자열을 저장하는 자료형은 없습니다. 

char 자료형으로 문자열을 출력할 경우

char 자료형으로 문자열을 출력하는 코드를 작성한 뒤 디버그 해보면 [0xC0000005: 0x00000000FFFFFFFC 위치를 읽는 동안 액세스 위반이 발생했습니다] 이 에러는 사용할 수 없는 메모리 주소 접근했을 경우 발생하는 에러 입니다. 

 

문자열은 char 포인터 형식으로 사용합니다.

char 포인터 선언
char 포인터
결과

문자(char)은 글자가 하나만 있는 상태를 의미하고, 문자열(char *)은 문자 여러 개가 이어진 상태를 의미합니다.

즉, 문자는 하나이기 때문에 (1byte)char에 저장할 수 있지만, 문자열은 크기가 1byte를 넘어서기 때문에 변수에 직접 저장하지 않고 

문자와 문자열의 저장방식

이처럼 포인터를 이용하여 저장합니다.

 

문자열 대신 문자열 주소를 출력하고 싶다면 서식지정자%p로 수정하면 됩니다. (애초에 포인터 변수저장했고 해당 변수 안에는 문자열이 있는 곳의 주소저장되기 때문에 굳이 다른 부분을 수정할 필요는 없습니다.)

문자열 메모리 주소 출력
결과

문자열 값이 있는 메모리 주소읽기 전용이므로 다른 문자열을 덮어쓸 수는 없습니다.

문제 코드

그저 눈으로만 살펴본다면 이 코드에는 큰 문제점이 없어보입니다. 하지만 실제로도 그럴까요? 한번 디버그를 통해 확인해 보시죠.

디버그 결과

방금 언급했던 것처럼 문자열 값 자체가 있는 메모리 주소읽기 전용이기 때문에 에러가 발생합니다.

 

문자열이 저장되는 위치컴파일러가 알아서 정하니 그것까지 신경쓸 필요는 없습니다. 그러니까 여러분들은 문자열을 사용할 때 char 포인터 " "(큰 따옴표)로 묶어야 한다는 점 그리고 문자열출력할 때는 %s 서식지정자를 사용한다는 점만 알고 계시면 됩니다.

 

아, 그리고 C언어의 문자열 마지막에는 항상 널문자(NULL)이 있다는 것을 기억하세요. NULL0으로도 표현가능하며, 문자열의 끝을 나타냅니다. 그래서 printf 함수문자열을 출력하면 NULL부분에서 출력을 끝내는거죠.

 

이렇게 문자열을 사용하다보면 간혹 어디가 이고 어디가 인지 헷갈리는 경우가 있어요. 일반적인 경우 다음과 같이

일반적인 경우

좌측, 우측로 표현합니다. 하지만 실제 메모리를 다루는 포인터 연산에서는 낮은 주소(0x00000000)뒤쪽(backward), 높은 주소(0xFFFFFFFF)앞쪽(forward)입니다. 그래서 뒤쪽으로 가는 것을 역방향, 앞쪽으로 가는 것을 순방향이라 표현합니다. (주소는 16진수(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, A, B, C, D, E, F)이기 때문에 저런 형태 입니다.)

포인터연산인 경우

여기서 문자열은 메모리의 낮은 주소부터 시작해 높은 주소까지 저장되어서 

포인터연산인 경우

우리가 일반적으로 생각하는 경우와 정반대가 됩니다. 그래서 저희는 메모리 주소의 앞 뒤를 구분하기 위해 문자열은 왼쪽(left), 오른쪽(right) 또는 처음(first), 마지막(last)이라는 말을 자주 사용하게 될겁니다.

 

이번에는 포인터를 인덱스로 접근해 요소를 출력해보겠습니다.

char 포인터를 인덱스로 접근
결과

여기서 공백 또한 하나의 문자로 생각한다는 점과 마지막 인덱스에는 널문자위치한다는 점만 주의하시면 큰 문제는 없을 겁니다. 

 

2. 문자열을 배열 형태로 선언하기

문자열은 char 포인터에서 저장할 수 있던것처럼 문자(char)배열에서도 저장할 수 있습니다.

문자 배열에 저장
char 배열 선언
결과

문자열 값 자체의 주소를 포인터에 저장하는 방식과 달리 문자열을 배열에 저장한다면 배열 요소 하나하나에 문자가 저장됩니다. 즉, 배열 안 문자들이 모여 하나의 문자열을 이루게 되는것이죠. 물론 배열이자 문자열이므로 인덱스가 0부터 시작하고 문자열의 마지막 부분에 널문자가 들어갑니다.

 

배열에 문자열NULL을 저장하고 남는 공간들은 신경쓰지 않으셔도 됩니다. 일반적으로 남는 공간에는 모두 널문자가 들어가기때문이죠. 배열문자열저장할 때는 선언함과 동시에 문자열초기화해야 합니다. 그러니까 배열미리 선언하고 나중에 문자열을 할당할 수 없다는 뜻입니다.

문자열 배열 할당 에러

그래도 하고 싶다면 배열 요소에 문자를 하나하나 할당하면 됩니다.

문자를 하나하나 할당
결과

다만 여기서, 널문자를 따로 할당해주지 않는다면 널문자가 나올 때까지 계속해서 출력하는 문제가 발생합니다. 

널문자를 할당하지 않는다면

이 경우에는 문자열이 짧다면 금방 할 수 있습니다. 다만 문자열의 길이가 길어진다면 좀 힘들겠죠? 그래서 선언된 배열에 문자열을 넣는 방법은 나중에 한번 다루겠습니다. 아,,, 혹시나 "문자열 값 자체를 저장한 주소읽기 전용이라 인덱스로 수정하지 못한다면서요..???"라고 말씀하시는 분이 있을까 미리 말해두겠습니다. 그건 char 포인터일 때 가져오는 문자열의 주소를 말하는 거지만 배열의 경우에는 주소가 아닌 문자 하나하나를 모아 문자열을 만드는것이기 때문에 변수에 값(주소가 아닌)을 저장합니다. 그래서 그거와는 상관없어요;;;

 

◎ 제가 쓴 글을 집중해서 읽으셨다면 아시겠지만 중요한거라서 한번만 더 강조할게요. 문자열을 저장할 배열선언한다면 문자열 + 널문자(\0)크기 지정해줘야 합니다. ex) Hello라는 문자열을 저장한다면 Hello(5) + 널문자(1) = 최소 크기 6

그리고 다른 배열과 마찬가지로 선언과 동시에 문자열을 할당한다면 따로 크기명시하지 않아도 되요.

크기를 명시하지 않는 경우
결과

char 배열 또한 인덱스문자에 접근해서 수정할 수도 있습니다만, 이전까지 배운게 있으시니 충분히 알거라 생각하고 생략하겠습니다.