본문으로 바로가기
반응형

C++에 C를 포함하고있기때문에 C++ 프로젝트로 새로 만들어준다.

 

Build가 Compile을 포함하고있다.

 

비쥬얼 스튜디오는 Build 용어를 사용한다.

.cs  는 C Sharp의 파일확장자명이다. 텍스트파일로도 열 수 있다.

.c  는 C 의 파일확장자명이다. 텍스트파일로도 열 수 있다.

 

최종적인 형태는 .exe 이다. exe는 execute(실행하다)의 약자이다.(이는 C와 C# 동일)

 

exe는 Binary 형태이다.(리눅스에서는 실행파일 모아둔곳이 Bin 디렉터리이다)

 

Windows에서의 실행파일은 Windows 디렉터리안에 모아두거나 프로그램 파일s 디렉터리에 넣어둔다.

Binary는 2진수라는 뜻으로 기계어로 되어있다는 것으로 해석할 수 있다.

2진수로써의 기계어는 On/Off(전압5V/전압0V)로 이루어져 있다. 참고로 폰은 3.3V/0V이다.

 

5V / 0V 일때는 노이즈가 생기게됬을때 0V일때의 잡음이 5V처럼 인식할 수도있기때문에 노이즈를 제거해주는 회로를 구성할 수 있다.

3.3V / 0V 일때는 노이즈에 더 취약해진다(0과 3.3V의 경계가 모호해졌으므로) 그래서 노이즈를 더 잡기위해 회로를 더 구성해야하고, 가격이 더 비싸진다.

 노이즈가 문제가 되는 이유는 0V를 0이 아니라 1로 인식할 수 있는게 문제가 된다. 그래서 따로 회로를 구상해서 노이즈를 잡아줘야할 것이다. 3.3V 회로가 노이즈에 더 취약한 이유는, 5V일때는 노이즈를 조금만 잡아줘도 0이 1로 인식하는 것을 막아줄 수 있겠지만, 3.3V일때는 5V에 비해 노이즈를 더 많이 잡아줘야 0을 1로 인식하지 않게 될 것이다.

 

 노이즈를 더 많이 잡으려면 그만큼 회로를 더 구상해야되고, 그렇게되면 가격이 더 비싸진다. 그래서 같은 성능을 기준으로 컴퓨터보다 휴대폰이 더 비싸다는 것을 추론할 수 있다.

 

 

c는 5가지로 나뉘어진다.

.c   .i   .asm  .obj  .exe

.c 와 .i와  .asm는 텍스트이고, .obj  .exe는 Binary이다.

 

 

CPU와 메모리의 구조는 다음과 같다.

 

 

int A = 100;    이라는 명령은 2가지가 들어있다.

1. int형 A를 만들어라.

2. A에 100을 넣어라.   라는 명령을 축약한 것이다.

 

나열해보면 다음과같다.

int = A;

A = 100;

 일의 양은 변하지않는다. 문장만 2개로 분리됬을뿐이지 실제로 컴퓨터에 실행할때는 영향을 주지 않는다.

C언어로 코드를 짤때는 영향을 주는지 안주는지 구별할 수 있어야한다.

 

 

int A = 2+3;   // 이 경우 명령어를 해석해보자.

1. int형 A생성

2. 2+3을 계산해라. 

   2-1  '2'을 레지스터에 넣는다.

   2-2  '3'을 레지스터에 넣는다.

   2-3  덧셈연산을 해라

3. 계산결과를 A에 대입해라.

 

 

.c의 최종형태인 실행파일(.exe)은 전원을꺼도 안전한 HDD/SSD 등의 기억장치에 저장되어있다.

이때 명령어의 경로를 좀 더 자세히 보면

 1. 기억장치에서 버스를 지나서 CU를 지나서 다시 버스를타서 명령어를 메모리의 Stack 영역에 A를 확보한다.

 2-1 1을 Cpu 레지스터 eax에 넣는다.

 2-2 3을 Cpu 레지스터 eab에 넣는다.

 2-3 4를 cpu 레지스터 eac에 넣는다.

 3. 레지스터에서 버스를타서 스택의 A에 4를 넣는다.

로 볼 수 있다.

 

이제 어셈블리와 메모리에서 명령어들이 실제로 어떻게 되는지 살펴볼 것이다.

 

일단 C++ 프로젝트 파일을 새로 생성해줘야하는데, 기능 및 설치에서 설치해주어야한다.

설치가 완료됬으면 C++ ,  Windows 에서 빈 프로젝트로 생성해준다.

 

 

 

 

 프로젝트 파일을 생성하면 아무것도 없는 것을 볼 수 있는데, 솔루션 탐색기 -> 소스파일 우클릭 -> 추가 -> 새 항목에서 파일을 추가해서 코드를 작성할 수 있다.

C++ 확장자는 cpp, c++, cc, cxx 등 여러가지인데, 비쥬얼 스튜디오는 cpp로 사용한다.

 

이름은 한글을써도 상관없지만 C를 사용하려면 cpp의 pp을 지워서 .c 확장자명으로 파일 이름을 정해줘야한다.

 

 

C++은 C문법, C의 전처리언어(Pre Process Langage : 앞에서 처리하는언어) 를 포함하고 있다. 

(참고로, #include처럼 #이 들어가면 C가아니고 전처리언어 문법이라는 뜻이다) 

 

 

C언어와 친해지기위해 해당 코드들을 작성해보자.

#include <stdio.h>  // #include는 C#에서 using과 비슷한 친구

//void main()
//int main() 
//int main(int a1, const char[] a2)  // main은 여러가지 모양이있다.

int main() 
{
	printf("지옥으로 키티");
}

printf 실행화면

 

 

#include <stdio.h>
int main() 
{
	printf("100\n100");
}

\n을 통해 한줄띄우기(엔터)효과를 낼 수 있다.

 

 

#include <stdio.h> 

int main() 
{
	printf("%d", 100);
}

%d로 정수 값을 받을 수 있다

 

참고로 d는 decimal의 약자로 십진수를 받는다는 뜻으로 생각해도 된다.

 

%x 는 hex로 16진수로 나타낼 수 있다.

%o 는 oct로 8진수로 나타낸다.

계산기로 봤을때는 100을 16진수, 8진수로 각각 64, 144로 되는 것을 확인할 수 있다

 

100을 C에서 16진수, 8진수로 출력해보자.

#include <stdio.h> 

int main() 
{
	printf("%d %x %o", 100, 100, 100);
}

계산기에서처럼 100을 16진수, 8진수로 바꿨을때 결과값이 출력되었다

 

 

 

 

 

 

 

이제 대충 C언어를 살펴봤고,

int A = 2 + 3;

을 어셈블리어와 레지스터영역, 메모리영역에서 확인해보자.

 

참고로, 어셈블리언어를 기계어로 바꾸는 것을 어셈블리라고한다.

기계어를 어셈블리언어로 바꾸는 것을 디스어셈블리라고한다.

 

 

우선 작성코드는 다음과같다.

#include <stdio.h> 

int main() 
{
	int A;
	int B = 3;
	int C = 2;
	A = B + C;   // 2 + 3은 컴파일러가 미리 계산해주기때문에 변수 값을 대입했다.
	printf("%d", A);
}

 

그리고 디버그 모드로 확인할 것이다. '디버그 -> 창 -> 메모리, 디스어셈블리, 레지스터'  를 클릭해서 창을 띄워서 확인해보자.

 

 

3개의 창을 다 띄우면 다음과같이 화면이 뜰것이다.

 

 

 

 

 

우선 어셈블리어를 살펴보자.

표시한 영역이 계산하는 부분이다

 

코드영역의 주소 mov는 move의 약자로, 이동시키라는 뜻이다.

dword ptr [B] 는 B의 주소로 이동시키라는 뜻이다. 

 

맨 오른쪽의 값들은 ','오른쪽의 값을 ','왼쪽에 넣으라는 뜻이다.

(예를들어, dword ptr [B], 3 으로보면, 3을 dword ptr[B]에 넣으라는 뜻이다)

 

따라서, int B = 3; 라는 뜻은 어셈블리어로 3이라는 값을 B로 이동시키라는 뜻이다.

 

mov      eax, dword ptr [B] 는 B를 eax에 넣으라는 뜻이다. -> B의 값 3을 CPU의 레지스터영역의 eax에 넣는다.

 

add는 덧셈을 하라는 뜻이다.

add       eax, dword ptr [C] 는 C를 eax에 넣으라는 뜻이다. -> eax값이 3+2 값인 5로 바뀐다.

 

mov      dword ptr [A], eax   는 eax를 스택영역의 A에 넣으라는 뜻이다. -> 메모리의 스택의 A가 5로 바뀐다.

 

 

 

 

 레지스터 영역과 메모리 영역에는 어떻게 바뀌는지 관찰해보자. 디버그 모드를 실행한채로 디스어셈블리창을 띄운후에 F10을 누르면서 레지스터영역과 메모리 영역의 변화를 관찰하면된다.(레지스터는 빅엔디안, 메모리는 리틀엔디안으로 저장한다.)

 

그리고 A가 어떤 영역에서 어떤 값으로 존재하는지 확인하기위해 메모리1 창의 주소를 &A로 쳐주자.

메모리창에 주소에 &A를 치니까 A가 할당된 스택영역의 주소가 나온다. 이미지는 B, C의 값도 확인하기위해 스크롤바를 위로 올렸다

지금 A가 할당된 스택영역의 주소는 0x00DDFD94 인것을 알 수 있다.(이 영역을 받는 것은 랜덤이다)

 

이제 F10을 누르면서 변화를 확인해보자. 노란화살표는 이 코드가 실행'예정' 이라는 뜻이다.

(= 노란화살표 위에 코드가 실행되었다는 뜻으로도 볼 수 있다)

 

메모리 영역에 3이 저장된 것을 볼 수 있다

 

 

메모리 영역에 2가 저장된 것을 볼 수 있다

 

A = B + C; 를 실행할 차례인데, 레지스터 영역 EAX에 B(=3)가 넣어진 것을 볼 수 있다. 

 

레지스터 영역 EAX에 C를 eax에 대입해줬는데, Add연산으로 인해 5가 넣어진 것을 볼 수 있다. 
eax를 A에 대입하라는 뜻인데 메모리 영역에 5가 저장되있는 것을 확인할 수 있다

 

 

지금까지 디스어셈블리로 

#include <stdio.h> 

int main() 
{
	int A;
	int B = 3;
	int C = 2;
	A = B + C;   // 2 + 3은 컴파일러가 미리 계산해주기때문에 변수 값을 대입했다.
	printf("%d", A);
}

 

이 코드를 살펴보았다. 그런데 왜 이렇게까지 자세하게 확인을 해야할까?

 

어셈블리, 디스어셈블리로 확인하면 내가 작성한 코드가 지금 컴파일러가 제대로 해석하고 있는지 확인할 수 있다.

-> 내가 코드를 그만큼 효율적으로 짰는지 확인할 수 있다.

 

예를들어, for문과 while문의 무한반복문의 코드를 보자. 

for문은 1줄로 끝나고. while문은 2줄이므로 무한 반복문을 사용할때는 for문이 더 효율적인 것을 볼 수 있다.

 

 지금은 하나씩만 작성해서 1개밖에 차이나지않지만, 나중에 코드 10만개를 작성했을때는 10만개 차이가 나기때문에 좀 더 효율적으로 코드가 잘 작성됬는지 확인하기 위해 어셈블리어를 확인하는 것이다.

 

 

 

c를 쓰는 이유?

해당 코드를 작성해서 디버깅모드로 관찰해보자.

#include <stdio.h> 
int main()
{
	int A1 = 1;
	int A2 = 2;
	int A3 = A1 + A2;
	int A4 = A3 + 6;
}

디스어셈블리로 확인하는 창

 

int A3 = A1 + A2;

int A4 = A3 + 6; 

의 디스어셈블리어로 확인해보면 거의 똑같은 명령이 된 것을 확인할 수 있다.

 

이 명령은 예를들어서 

int A = 3;

int B = A;

A = B;     // 어자피 A = B인데 굳이 A=B;  를 선언해서 한 코드를 더 작성해준 것이다.

 

빌게이츠는 이 중복되는 명령을 없앴다고하는데 그에관한 이야기는 다음시간에 계속된다고 한다.

반응형