일반적 언어는 개발자에게 제약을 주지만(실수를 최대한 줄이게) C언어는 프로그래머가 자유롭게 코드를 짤 수 있게 해놓음(내부적인 하드웨어 수준의 조작이 가능)
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, 운영체제 등을 신경쓰게 됨, 바이너리는 실행명령어들의 문서파일
바이너리(오브젝트, 타겟) : 실행파일의 일종, 프로시져의 묶음
바이너리 파일에 기계어가 들어있음
텍스트파일이 아니라서 바이너리라고 하였고 소스파일의 대상이 되니까 오브젝트(대상)이라고 함
컴파일 : 소스코드를 바이너리로 변환
링크 : 바이너리 파일을 묶어서 실행가능한 형태로 변환
프로시져 : 메인함수 - 메인함수가 아님
프로시져 = 함수
파이썬의 함수와는 다른 low level에 있는 함수(하나의 실행단위)
#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;
}
타자연습 빠르게 해야 개발 실력이 늘음
#include <iostream>
int main(){int a = 1;float b = 2;double c = 3;
printf("%d %f %lf\n", a, b, c);
return 0;
}
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 오프젝트 파일
파이썬 함수는
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차원으로 변경가능하다