티스토리 뷰

대외활동/Tips 19기 강좌 정리

[19기 TIPS] 2018.07.09. 5차

시골청년개발자 2018. 7. 12. 17:42

와... 다 썼는데 다 날라갔어요.

엄청 열심히 썼었는데 ㅠㅠ 다시 씁니다.

 

 

1. 조별 과제

 

이번에 조별 과제가 있었는데 저희 조는 시프트 연산자, 논리 연산자, 비트 연산자를 써서 다양하게 코드를 짰었습니다. 점점 더 효율적으로 코드를 만들기 위해 시프트 연산자와 비트 연산자만 사용해서 만들 수 있었는데요. 그 코드에서 아래의 코드와 같이 시프트 연산자를 2번만 써서 만들 수도 있었습니다.

(변수 i는 for문을 돌리기 위한 변수였습니다.)

str[i] = ((unsigned char)(data << i) >> 7) + '0';

 

저 코드에서 unsigned char로 형변환을 안 해주면 안 됩니다. 실행은 되는데 생각과는 다른 값이 나옵니다.

[ data << i ] 연산을 할 때 연산의 결과가 특정 변수에 대입되지 않기 때문에 자동으로 int형으로 형변환이 되는 것입니다.

 

컴퓨터는 1바이트 연산을 안 한다고 합니다. 기본적으로 2바이트, 4바이트, 8바이트를 씁니다. 우리가 문자 1개를 저장할 때 1바이트로만 사용하면 되지만 실제로 연산은 1바이트로 일어나지 않습니다. 우리가 연산하는 것과 컴퓨터가 연산하는 것이 다른 거죠.

unsigned char a;
a = (data << 5) >> 7) + '0';

 

위의 코드에서 data를 2진수 [ 0111 0101 ]이라고 가정해보겠습니다. 저기섯 5를 연산하면 [ 1010 0000 ]이 되는 것이 아닙니다.

이유는 위에서 말했듯이 자동으로 int형으로 형변환이 되어서 [0000 0000 0000 0000 0000 1110 1010 0000 ]이 되는 것입니다. 저기서 연산을 다 한 후에 a로 대입될 때는 char형으로 형변환이 되고 대입이 됩니다. 정확히 말하면 LSB에서 가까운 1바이트만 들어갑니다. 나머지는 잘리게 되는 거죠. 이 방법에서의 주의할 점은 int형이 unsigned int형이 아닌 그냥 int형으로 된다는 것입니다.

str[i] = 48 - ((data << (24 + i)) >> 31);

 

위의 코드는 형변환을 안 하고 (data << (24 + i)) 연산을 할 때 4바이트로 된다는 걸 생각하고 짠 코드입니다.

저렇게 하면 형변환을 안 할 수 있습니다.

 

그렇기 때문에 이번 조별 과제의 목적은 "우리가 생각하는 연산자와 컴퓨터가 쓰는 연산자가 다를 수 있다" 는 것이라고 생각합니다. 또한 이번 조별 과제를 통해 생각하는 부분에서 많이 부족하다는 것을 느꼈습니다. 다양하게 생각하는 것을 기를 수 있는 방법을 찾아봐야겠습니다.

 

2. 차원 배열

 

시작하기 전에 사실 2차원 배열은 C언어가 제공하는 문법이 아닙니다. 보통 저희가 배울 때 1차원, 2차원, 3차원 나눠서 배우지만 사실 배열은 1차원만 존재합니다. 1차원 배열 외의 차원 배열은 형태적으로 존재하는 것이 아니며 컴파일러가 제공하는 것입니다. 컴파일러가 조작해서 2차원, 3차원이 있는 것처럼 보이는 것이죠.


1차원 배열은 전에 포스팅한 글에서 설명 했으니 2차원 배열에 대해 설명해보겠습니다.

아래는 2차원 배열의 선언 방법입니다.

int data[y][x];

 

헷갈릴 수도 있겠지만 2차원 배열에서 채워지는 것은 행부터 채워지지만 표기를 할 때는 y축, x축입니다.

즉, 행부터 채워진다는 것이죠.

행은 가로, 열은 세로인거 아시죠? 아실거라 믿습니다.


2차원 배열의 예시를 들어서 설명해보겠습니다.

data[5][4]을 그림으로 표현해보자면 아래와 같이 됩니다.


위의 형태는 2차원이지만 실제로 메모리에 저장되는 형태는 아래처럼 1차원의 형태로 저장됩니다.

(노란색은 보기 힘들어서 보라색으로 바꿨습니다.)

 

이렇게 컴퓨터가 사용하는 메모리에서는 2차원이라는 개념을 제공하지 않습니다. 위의 형태처럼 1차원 형태로 메모리에 저장이 되는 것입니다. 그렇기 때문에 위의 data[5][4]는 data[20]과 같습니다.

 

결국 1차원 배열을 쓸지 2차원 배열을 쓸지는 컴파일러에 얼마나 의존하느냐에 따라 달라집니다. 둘의 차이가 엄청 크다고 합니다.

저도 컴파일러에 의존적이지 않은 개발자가 되기 위해 노력해야겠다고 생각이 들었습니다.

 

// n과 a, b는 구하고 싶은 배열의 번호이다.
// i는 행을 뜻하며 j는 열을 뜻한다.

/* 1차원(arr1[n]) → 2차원(arr2[a][b]) */
arr2[n / b][n%b]
/* 2차원(arr2[a][b]) → 1차원(arr1[n]) */
arr1[i*a + j]

 

/* 0~5번 배열에 값 대입  */

/* 1차원 배열 사용 */
int main()
{
	int temp[6];
	for (int i = 0; i < 6; i++) {
		temp[i] = i; 
	}
}

/* 2차원 배열 사용 */
int main()
{
	int data[2][3];
	for (int a = 0; a < 2; a++) {
		for (int b = 0; b < 3; b++) {
			data[a][b] = a * 3 + b;
		}
	}
}


위의 코드를 봄으로써 1차원 배열을 사용하는 것이 더 보기 편하다는 것을 알 수 있습니다.


3. 포인터


<직접 주소 지정 방식>

이미 전의 글에서부터 쓰던 "변수"를 이용해서 주소를 지정하는 방식이 직접 주소 지정 방식입니다.

메모리를 사용할 때 프로그래머가 사용할 메모리 주소를 직접 적는 방식입니다.

예를 들어서 "100번지에 150이라는 값을 4바이트 크기로 저장하겠다."라는 것입니다.

저런 비슷한 예시를 제가 작성한 '2차 강의 정리' 글에서 보셨을 것입니다.

즉, 직접 주소 지정 방식은 C언어의 "변수" 문법과 같은 것입니다.

그리고 그게 알고보면 메모리를 사용하는 포인터와 관련이 있는 것이죠.

직접 주소 지정 방식은 명령어가 기계어에 포함되어 있기 때문에 주소를 바꿀 수 없다는 단점이 있습니다.

그렇지만 간접 주소 지정 방식을 사용하면 프로그램이 실행 중에도 얼마든지 주소를 바꿀 수 있습니다. 

그렇기 때문에 간접 주소 지정 방식이 융통성이 있다고 하는 것입니다.


<C언어>

C언어는 구조화된 언어라는 특징을 갖고 있습니다. 전에도 말했듯이 함수를 기반으로 된 언어입니다.

그렇기에 직접 주소 지정 방식은 변수 개념을 사용하기 때문에 문법 구조상 서로 다른 함수에 존재하는 변수에 대해서는 참조가 불가능합니다. 그렇기 때문에 직접 주소 지정 방식의 한계가 발생하게 됩니다. 즉, 격리 현상이 일어난다는 것이죠. 함수를 호출할 때 넘기는 매개변수의 값은 제한이 없습니다. 그렇지만 다른 함수가 호출될 때는 한 개만 갖고 호출됩니다.

이러한 현상을 해결하기 위해서 포인터를 이용하여 간접 주소 지정 방식을 사용하는 것입니다.


<포인터>

포인터는 기계어가 제공하는 명령어입니다. 그 명령어는 컴파일러가 사용하는 것이고요.

일반 변수도 주소를 저장할 수는 있지만 저장된 주소의 메모리에 가서 값을 읽거나 저장하는 간접 참조 기능은 없습니다.

그렇기 때문에 C언어는 간접 주소 지정 방식으로 작동하는 포인터라는 문법을 제공합니다.

자신이 사용하고 싶은 메모리의 주소를 저장하고 있는 메모리가 포인터입니다.

대부분 포인터를 제외하고 사용하는 방법은 직접 주소 지정 방식입니다. 그렇지만 포인터를 사용하여 간접 주소 지정 방식을 사용하면 포괄적이며 융통성이 좋아집니다. 그 대신 속도가 비교적 느립니다.

어떤 행위를 하는데 그 행위의 대상이 누군지 정해놓지 않고 할 수 있다는 것이 포인터의 가장 큰 장점입니다.

 

<포인터의 크기>

포인터는 자료형을 따로 선언하지 않아도 무조건 크기가 4바이트나 8바이트를 갖고 있습니다. 크기를 갖는 기준은 몇 비트 OS를 쓰고 있느냐에 따라 달라집니다. 32비트 운영체제이면 4바이트의 크기를 갖고 64비트 운영체제이면 4바이트, 8바이트 크기를 둘다 가질 수 있습니다. 64비트 운영체제이면 프로그래밍을 하기 전 비쥬얼 스튜디오에서 x86과 x64를 선택할 수 있습니다. 참고로 x86은 32비트를 의미합니다.


64비트로 프로그래밍을 하기는 하지만 대부분 호환성 때문에 32비트로 프로그래밍을 합니다.

물론 64비트로 프로그래밍을 해도 되긴 합니다만 현재 보편적으로 32비트를 사용하기 때문에 저도 32비트 운영체제를 기준으로 설명하겠습니다.

 

< * >

포인터 변수에 " * "를 쓰는 이유는 무엇일까요?

그 이유는 일반 변수와 구별하기 위해서 사용하는 것입니다.

포인터는 포인터 변수 이름 앞에 ' * '를 붙임으로써 다른 변수를 참조할 수 있습니다.


선언할 때의 *는 연산자가 아니고 형식 지정 식별자입니다.

그리고 선언할 때가 아닌 경우에서의 *는 번지 지정 연산자입니다.

short *p; // 형식 지정 식별자, p가 포인터이다. p의 자료형: short 포인터

*p = 3; // 번지 지정 연산자, p가 저장하고 있는 주소에 3을 대입하겠다.

 

<& 연산자>

& 기호는 주소를 반환하는 번지 계산 연산자라고 합니다.

 

<간접 주소 지정 방식>

위에서 설명을 했듯이 간접 표현은 직접 표현보다 포괄적이며 융통성이 좋습니다.

예를 들어 "100번지에 4바이트 크기의 주소가 저장되어 있는데 그 주소로 가서 150이라는 값을 2바이트 크기로 대입하겠다."라는 것입니다.

간접 주소 지정 방식을 사용하는 이유는 위에서도 말했듯이 다른 함수에서도 값을 참조하기 위해서 입니다.


아래는 포인터를 이용한 예제입니다.


#include <stdio.h>

void Test(short *ptr)	// tips의 주소를 4바이트의 크기로 ptr의 값에 대입
{
	short soft = 0;	// soft에 2바이트 크기로 0을 대입
	soft = *ptr;	// ptr에 저장된 주소에 가서 그 값을 soft에 대입
	*ptr = 3;	// ptr에 저장된 주소에 가서 3을 대입, tips = 3
}

void main()
{
	short tips = 5;	// tips에 2바이트 크기로 5를 대입
	Test(&tips);	// Test 함수에 tips의 주소를 매개변수로 사용
}

 

아래는 스왑 함수의 예제입니다.

간접 참조 방식을 이용하여 start 값과 after 값을 바꾼다는 것이죠.

 #include <stdio.h>

void Swap(int *pa, int *pb)	// start와 end의 주소를 4바이트 크기로 각각 pa, pb의 값에 대입
{
	int temp = *pa;	// pa에 저장된 주소에 가서 그 값을 temp에 대입
	*pa = *pb;	// pb에 저장된 주소에 가서 그 값을 pa에 저장된 주소에 대입
	*pb = temp;	// temp의 값을 pb에 저장된 주소에 가서 대입
}

int main()
{
	int start = 96, end = 5;	// start, end에 4바이트 크기로 96과 5를 각각 대입
	printf("before : start = %d, end = %d\n", start, end);	// 출력
	if (start > end) {	// start가 end보다 크면 아래 명령문 실행
		Swap(&start, &end);	//start의 주소와 end의 주소를 매개변수로 사용
	}
	printf("after : start = %d, end = %d\n", start, end);	// 출력
}
(이해를 돕기 위한 메모리 그림은 나중에 다시 추가하겠습니다.)

결과를 보면 잘 바뀌어져 있는 것을 확인할 수 있습니다.






'대외활동 > Tips 19기 강좌 정리' 카테고리의 다른 글

[19기 TIPS] 2018.07.16. 7차  (2) 2018.07.19
[19기 TIPS] 2018.07.12. 6차  (2) 2018.07.16
[19기 TIPS] 2018.07.05. 4차  (2) 2018.07.09
[19기 TIPS] 2018.07.02. 3차  (2) 2018.07.04
[19기 TIPS] 2018.06.28. 2차  (2) 2018.06.29
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2025/07   »
1 2 3 4 5
6 7 8 9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29 30 31
글 보관함