C언어

nahye·2021년 5월 4일
0

C언어


  • 리눅스 개발을 위해 만든 언어
  • 하드웨어 친화적이기 때문에 - 기계어를 약간 편리하게 쓰는 수준
    • 메모리에 직접 접근
    • 운영체제, 드라이버, 임베디드 등 못 만드는 게 없음
    • 최적화에 매우 유리함(사람마다 관점 다름) - 일반적으로 속도가 빠르다
  • 파이썬의 하위언어(파이썬 수발드는 언어..)
  • 우리는 개발자를 믿는다.(C언어의 모토)
    일반적 언어는 개발자에게 제약을 주지만(실수를 최대한 줄이게) C언어는 프로그래머가 자유롭게 코드를 짤 수 있게 해놓음(내부적인 하드웨어 수준의 조작이 가능)
  • 다양한 플랫폼(CPU, 운영체제)을 지원?
    => 각 운영체제에 대한 Compile을 해야함
    C언어는 중간에 거치는 게 없이 운영체제와 맞닿아 있다. 소스코드를 만들면 각 운영체제에 맞게 Compile을 해야한다.
    C언어 개발 중심으로 볼 때 Python은 프로그램 위에서 돌아가는 스크립트 뿐이다.
    예를 들어 스타 유즈맵으로 게임을 만들면 맵파일을 불러올 때 다양한 운영체제에서 불러오는데 이때 스타의 맵파일은 크로스컴파일이 가능하다고 볼 수 있다. 파이썬도 이와 동일하다.
    프로그램은 CPU, 운영체제를 신경쓰게 되는데 (임베디드 경우엔 운영체제 신경X) 이 두 가지를 Compile을 해줘야함, CPU를 개발했을 때 테스트를 해봐야한다. C언어를 기계어로 번역하는 Compiler를 해야한다.
    세상에 모든 기계는 C언어를 지원하고 있다.
    C언어는 이 세상의 모든 플랫폼을 지원한다.
  • 다양한 변종이 존재 문법체계가 허술함

속도가 중요시 되는 분야 : 게임(극도의 퍼포먼스를 최적화시켜야해서), 인공지능(겉만 파이썬이고 내부는 C언어-텐서플로우로 되있어서 속도 빠름)

파이썬을 쓸 때 1-100수를 더한다.

sum=1
for i in range(100):
	sum+=i
import numpy as np
total = np.arange(1,101).sum()
total = sum(range(1,101))


컴파일 과정

  • 파일 : 실행파일 and 실행파일 아님

  • 실행파일 만드는 과정 : 소스파일 ->(컴파일) 바이너리 ->(링크) 실행파일
    소스파일을 바이너리로 옮기는 게 컴파일, 컴 CPV, 운영체제 등을 신경쓰게 됨, 바이너리는 실행명령어들의 문서파일

  • 바이너리(오브젝트, 타겟) : 실행파일의 일종, 프로시져의 묶음

    • CPU, 운영체제에 의존적
      바이너리 파일에 기계어가 들어있음
      텍스트파일이 아니라서 바이너리라고 하였고 소스파일의 대상이 되니까 오브젝트(대상)이라고 함
  • 컴파일 : 소스코드를 바이너리로 변환

  • 링크 : 바이너리 파일을 묶어서 실행가능한 형태로 변환

  • 프로시져 : 메인함수 - 메인함수가 아님

    • 링크시 반드시 하나의 메인함수가 필요(엔트리)
      프로시져 = 함수
      파이썬의 함수와는 다른 low level에 있는 함수(하나의 실행단위)


Hello World 예제

#include <iostream>

int main()
{
    
    int a = 1;
    float b = 2;
    double c = 3;
    
    printf("Hello world\n");
    printf("a = %d, b = %f, c=%lf\n", a, b, c);
    
    return 0;
}

타자연습 빠르게 해야 개발 실력이 늘음

  • C언어는 띄어쓰기가 필요없음
#include <iostream>

int main(){int a = 1;float b = 2;double c = 3;
    printf("%d %f %lf\n", a, b, c);
    return 0;
}
  • 한 줄이 끝날때는 ;(세미콜론)을 붙여야 함
  • 시작과 끝엔 {} 해주기
  • 큰 따옴표("")만 문자열로 해주기
  • C언어는 문자열 formatting을 안 해주고 printf 함수 내부속에 들어있기 때문에 a,b,c 이렇게 표현
  • 직접 문자를 개행해줘야 함(%d %f %lf\n)
  • 변수를 쓰고 싶으면 미리 선언해주고
    선언을 할 때 어떤 타입인지 적어줘야 함
    • int라고 쓰면 int만 와야함(타입에 맞아야 함)
    • python은 모두 void 타입이라 여러가지 저장 가능

C언어에서 float은 4byte



함수예제

#include <iostream>
void func(void)
{
    printf("void function\n");
}
int func2(int a, int b);
int main()
{
    func();
    int sum;
    sum = func2(3, 4);
    printf("3 + 4 = %d\n", sum);
}
int func2(int a, int b)
{
    return a + b;
}
#include <iostream> 

void func1()
{
    printf("called func1\n");
}

int add(int a, int b); //함수 선언

int main() //반환값 없는 함수 정의
{
    func1();
    printf("%d\n", add(3,4)); //add함수 호출
}
int add() //add 함수 만듬
{
    return a + b;
}

C언어 함수 = 선언, 정의
선언 : 이런 함수가 있다고 하자(있다 치고) - 컴파일,번역 가능

컴퓨터 조립할 때 메인보드, CPU, 파워, RAM 등 필요 메인보드 만드는 회사는 CPU, 파워 등이 필요없고 규격만 있으면 소켓 부분을 만들 수 있다.
여기서 규격이 선언
즉 메인보드를 만드는 과정이 컴파일이고 최종 컴퓨터를 만드는 과정이 링크
add함수는 없었는데 일단 호출을 해 프로그램을 짜고 이 다음 add 함수를 만듬

C언어에서 함수는 정의와 선언이 따로 구현됨

선언만 모아놓은 파일 : 헤더파일
ex) .h, ~.hpp 등
헤더파일만 있으면 일단 compile이 됨

OpenCV 오프젝트 파일



파이썬 함수와 비교하여


  • 파이썬 함수는

    • 인자, 리턴의 타입이 없다.
    • 튜플을 통해 2개 이상의 값을 리턴할 수 있다.
    • 파이썬이 직접 관리한다.
  • C언어 함수는

    • 운영체제 수준에서 관리한다.
      직접적으로 연결됨
    • 선언만 있으면 컴파일이 가능하다.
    • 링크시에는 구현이 있어야 한다.


배열예제

#include <iostream>

int main()  //stack영역
{
    int a[5]; // int형을 담을 메모리를 할당해라
    //운영체제로부터 메모리 공간을 받아온 것
    for (int i = 0; i < 5; i++)
    {
        a[i] = i;
    }
    int b[3][5];
    for (int y = 0; y < 3; y++)
    {
        for (int x = 0; x < 5; x++)
        {
            b[y][x] = y * a[x];
        }
    }
}

메모리 능력을 늘리는 것이 딥러닝의 사고능력을 기르는 것과 직접 연관이 생긴다.

운영체제가 메모리에 할당받아야 프로그램이 실행가능
운영체제에 메모리를 요구해야함

프로그램은 운영체제에 대해 메모리를 요구하고
메모리를 쓸 때마다 운영체제에 요청하는 식으로 되어있음
항상 운영체제의 감독하에 메모리를 쓰게 되어있다.
프로그램이 바로 메모리를 쓸 수 없음.

메모리는 항상 선형(직선)으로 되어있음
메모리는 테이프와 같음
그냥 노트는 위치를 바로 파악할 수 있지만
컴퓨터가 쓰는 메모리는 긴 한 줄짜리 노트같이 되어있음
C언어는 주소값을 직접 다루는데 이는 상대적 주소값
상대적 주소값 : 프로그램은 모든 주소를 다 보지 못하고 운영체제가 할당해주는 부분을 주소체계를 다시 0부터 매겨 절대적인 주소값을 생성

32bit -> 64bit
주소의 길이가 4byte
RAM 4G까지 가능

#include <iostream>

int main()
{
	int a[5];
    
    for (int i =0; i<5;i++)
    {
    	a[i] = i;
    }
    
    int i = 0;
    while(i<5)
    {
    	a[i] = i;
        i += 1;
    }
    
    int b[3][5]; //이중배열
    
    for(int y=0; y<3; y++)
    {
    	for(int x=0; x<5; x++)
        {
        b[y][x] = a[x] + y;
       	}
    }
}

C언어는 요소를 추가, 삭제하는 게 불가능

2차원 행렬을 다루기 어려우니
내부적으론 1열로 되어있으니까
2차원 행렬 문법으로 선언하지 않고
1차원 행렬 문법으로 접근해 인덱스를 계산함

논리적으론 2차원이지만 물리적으론 1차원!



포인터

  • 주소 값을 저장하는 변수의 일종

  • 32bit 프로그램에서는 4byte, 64bit 프로그램에서는 8byte이다.

    • 64bit 운영체제에서도 32bit 프로그램은 4byte 크기이다.

      요즘은 4byte보다 8byte로 저장함

  • 증감 연산 시 타입의 크기만큼 증감된다.

  • 배열처럼 활용할 수 있고, 문법도 배열과 유사하다.

  • 포인터는 주소값만 있을 뿐 다른 메타 데이터가 없으므로 사용자가 직접 메타 데이터를 동원해야 함

    • 3x5 이미지 공간을 만들었을 때, 하드웨어 수준에서 봤을 땐 1x15인지, 3x5인지 알 수 없음(하드웨어 수준에선 단순히 숫자가 나열된 거로만 보임) 누군가가 설명을 해줘야하는데 그게 메타데이터, 즉 알맹이와 메타데이터가 둘 다 필요

      포인터로만은 알 수 없으니 메타 데이터가 필요

      메타데이터 혹은 헤더 라고 부름


포인터 예제

int main()
{
	int a = 10;
	int* p = &a; 
    //&를 넣으면 주소값을 저장하는 공간으로 바뀜(&: 주소값을 리턴해줌), 가장 앞에 있는 주소값을 꺼내옴
	printf("%d, %d, %d, %d/n", a, &a, p, &p, *p);
	char* c = (char*)&a;
	c[0] = 'a'; c[1] = 'b'; c[2] = 'x'; *(c + 3) = '\0'; 
    // \0을 그냥 0이라고 적어도 됨
	printf("%s\n", c);
	printf("%s\n", c + 1);
	printf("%s\n", c + 2);
}


포인터에 주소값을 넣게 됨

포인터(p)를 저장해서 주소값(56)을 저장할 수 있고 그 주소값(92)을 또 저장할 수 있다.

*p를 출력하게 되면 주소값(56)을 참조해 a의 실제값(10)을 출력
( & <-> *)
주소값(&)을 꺼내냐 실제 값(*)을 꺼내냐의 차이
int main()
{
	int a = 10;
	int* p = &a; 
    //&를 넣으면 주소값을 저장하는 공간으로 바뀜(&: 주소값을 리턴해줌), 가장 앞에 있는 주소값을 꺼내옴
	printf("%d, %d, %d, %d/n", a, &a, p, &p, *p);
    *p = 20;
    printf("%d\n", a);
    
	char* c = (char*)&a;
	c[0] = 97; c[1] = 'a'+1; c[2] = 'x'; *(c + 3) = '\0'; 
   
	printf("%s\n", c);
	printf("%s\n", c + 1);
	printf("%s\n", c + 2);
}

''를 쓰면 문자인데 'a'대신 97이라고 적어도 됨
타입은 int지만 문자열로 넣는 공간으로 써야지 하면 char로 적으면 된다.


*p = 20;
    printf("%d\n", a);

p는 20이라면 56이 아닌 20을 적게 되고 (p= &a)
p포인터에 20을 넣는다.(*p = 20)

char* c = (char*)&a;
	c[0] = 97; 
   *c = 97;

내부적으론 포인터 연산이 일어나고 있다.

int main()
{
	int a = 10;
	int* p = &a;
    printf("%d, %d\n", p, p+1);
    

4byte 증가함

차이가나는 이유는 +1을 했을 때

배열을 다룰 때 int형으 4byte라 메모리가 구성되어 있기 때문에 +1을 했을 때 바로 다음 곳을 접근하는 게 아닌 다음 4byte 공간에 접근


mat example

#include <iostream>

struct Mat {
	int cols;
	int rows;
	float* data;
};

// struct는 class의 미니버전
	Mat 12byte 할당

Mat* create_mat(int rows, int cols)
{
	Mat* mat = (Mat*)malloc(sizeof(Mat));
	mat->rows = rows;
	mat->cols = cols;
	mat->data = (float*)malloc(sizeof(float) * cols * rows);
	return mat;
}

void release_mat(Mat* mat)
{
	free(mat->data);
	free(mat);
}

void print_mat(Mat* mat)
{
	int rows = mat->rows;
	int cols = mat->cols;
	
	float* data = mat->data;
	printf("Mat(%d, %d)\n", rows, cols);
	for (int y = 0; y < rows; y++)
	{
		for (int x = 0; x < cols; x++)
		{
			printf("%.2lf  ", data[y * cols + x]);
		}
		printf("\n");
	}
}

void set_data(Mat* mat, const float* src)
{
	int size = mat->cols * mat->rows;
	float* dst = mat->data;
	for (int i = 0; i < size; i++)
	{
		dst[i] = src[i];
	}
}

void dot(Mat* src1, Mat* src2, Mat* dst)
{
	for (int y = 0; y < dst->rows; y++)
	{
		for (int x = 0; x < dst->cols; x++)
		{
			float sum = 0;
			for (int i = 0; i < src1->rows; i++)
			{
				sum += src1->data[y * src1->cols + i] *
					src2->data[i * src2->cols + x];
			}
			dst->data[y * dst->cols + x] = sum;
		}
	}
}

int main()
{
	printf("running...\n");
	Mat* mat1 = create_mat(2, 2);
	Mat* mat2 = create_mat(2, 2);
	Mat* dst = create_mat(2, 2);
	float d1[] = { 1, 0, 0, 1 };
	float d2[] = { 1, 2, 3, 4 };
	set_data(mat1, d1);
	set_data(mat2, d2);
	dot(mat1, mat2, dst);
	print_mat(mat1);
	print_mat(mat2);
	print_mat(dst);
}

행렬처럼 생각해도 됨
리스트와 동일 개념

논리적 2차원 행렬을 물리적 1차원으로 변경가능하다

profile
Slow and steady wins the race

0개의 댓글