-
[C언어] 18-06-11 ~ 18-06-20교육/한컴MDS 2019. 1. 7. 10:59
18-06-11
프로그램 정의 : 가공되지 않은 데이터를 알고리즘을 이용해서 목적에 맞는
임베디드SW 개발단계에서 해당 target 보드 플랫폼을 위한 크로스컴파일 환경 구축이 우선적임
컴파일러와 링커
컴파일러 : 일반적으로 .c 파일의 소스코드를 object 파일로 변환하는 과정
링커 : 컴파일러에 의해 생성된 object 파일을 통합하여 하나의 실행파일을 만드는 과정
임베디드sw에서는 니블(4bit) 단위를 사용함
제어문자
\n 개행, 줄을 바꿈 (0x0A)
\r 캐리지 리턴, 출력위치를 줄의 맨 앞으로 이동 (0x0D)
18-06-12
printf("%s", "embedded")에서 %s는 문자열 상수의 주소에서 널문자를 만날때까지의 문자를
대변함
"embedded"와 같은 문자열 상수는 메모리상에 embedded\0 으로 저장되며,
'\0'는 널문자로 숫자 0으로 정의되어있다.
scanf("%d", &nVal); // scanf 기본형태
scanf("%d %d", &nVal1, &nVal2); // 숫자 2개 입력
scanf 입력파라미터에 주소값을 넘겨주는 이유는 해당 변수 주소에 입력값을 넣기 위함
little endian : 낮은 주소값에 낮은 비트값이 저장되는 형태
big endian : 낮은 주소값에 높은 비트값이 저장되는 형태
and gate : 곱
or gate : 합
buffer(delay) : 지연
not : invertor 역할
xor : 배타적합으로 다른값일경우 1로 출력
3-state buffer : 하나의 라인으로 입/출력을 설계하는 도구
char 표현 범위 : -128 ~ 127 (256) => '1000 0000 (-128)' ~ '0111 1111 (127)'
unsinged char 표현 범위 : 0 ~ 255 (256)
비트연산자
| : or
& : and
~ : not
<< : left shift
>> : right shift
^ : xor
ex) res &= ~(0x01 << 7)
나머지연산자(%)
사용처 : 홀/짝구분, 원형큐
원형큐에서는 배열 크기가 5라고 가정하면, 인덱스를 index %= 5 라는 형태로 사용함
18-06-13
많은 양의 데이터를 저장하고 처리하기위해 배열이 존재함
배열의 크기 = sizeof(배열명)/sizeof(배열자료형)
배열의 선언 및 초기화
int Data[5] = {0};
int Data[5] = {1,2};
초기값을 인덱스보다 적게 표기할경우, 나머지 인덱스는 0으로 자동채워짐
배열명은 배열의 시작주소
배열명은 포인터상수
배열명이 Data일때,
Data == 0x2000 == &Data[0]
문자배열이 아래와 같이 선언되어있으면,
char Src[20] = "abcd";
이 문장은 아래와 같은 문장이 된다.
char Src[20] = {'a', 'b', 'c', 'd', '\0'};
문자배열은 항상 마지막에 널문자를 생각해서 사이즈를 생각해야함
문자열 복사는 strcpy
문자열 입력받는 gets
문자열을 출력하는 puts
함수명은 함수가 정의된 메모리의 시작주소
함수는 직접호출과 간접호출이 있는데, 간접호출이 함수포인터
직접호출은 정의된 함수명을 사용해서 함수를 사용하는것이고,
간접호출은 함수명이 함수의 시작주소임을 이용해서 사용하는것
폴링은 함수호출에 의해 동작하는 것이고,
인터럽트는 이벤트가 발생하면 ISR이 동작하는 형태
상태 표시
0 = false, off, 0V, clear, low
1 = true, on, 5V, set, high
지역변수
해당 함수안에서만 접근
해당함수 시작과 동시에 메모리에 load되었다가 함수종료와 함께 메모리에서 해제
함수호출형식 (3가지)
call-by-value : 값의 복사
call-by-address : 포인터를 이용
call-by-reference : 레퍼런스변수를 이용 (C++에서 사용되는 호출방식)
일반변수 = 데이터;
포인터변수 = 주소;
32-bit 시스템에서 모든 포인터 변수는 자료형에 상관없이 4byte의 크기를 가짐
아래와 같이 정의했을때,
int tmp = 3;
int* ptr = &tmp;
ptr의 자료형은 int*(포인터)
*ptr의 자료형은 int
&ptr의 자료형은 int**(더블포인터)
18-06-14
포인터의 초기화
int* ptr = NULL;
NULL은 0으로 정의되어있으며, 널포인터라고 함
char* pc; // 4byte
int* pn; // 4tye
double* pd; // 4byte
포인터변수는 32비트 시스템에서 4byte의 크기를 가지는데, 각각 자료형을 구분한 이유는
포인터변수의 자료형이 시작주소부터 몇 byte를 참조할것인지를 결정하기 위함
int arr[3] = {0};
int* pA = arr;
일경우, *(pA+i) == pA[i] 공식이 성립함
변수의 상수화
const int temp = 5;
#define PI 3.14 (전처리기를 이용한 매크로 상수)
포인터변수의 상수화
int val = 7;
int temp = 5;
const int* pt = &temp;
*pt = 3; // (X) 해당 주소의 값 수정 불가
pt = &val; // (O) 저장된 주소값 변경 가능
int* const pt = &temp;
*pt = 3; // (O) 해당 주소의 값 수정 가능
pt = &val; // (X) 저장된 주소값 변경 불가능
strcpy(const char* pS, ...) 등..의 라이브러리 함수를 보면
'const char* XX' 형태를 많이 쓰는걸 볼수있는데,
의미상으로 해당 포인터 변수를 Read용으로만 사용하며,
Write 용도가 사용하지 않겠다는 의미이다.
문자열상수와 문자열배열의차이
문자열상수("embedded")는 생성과 동시에 메모리에 올라가면서 시작주소를 반환하기에
char* pS = "embedded"; 로 선언하며 문자열상수이므로 해당 문자열은 변경 불가능
ex) *(pS+0) = 'k' 라는 문장은 불가능함
*pS++ 와 같은 형태는 사용 가능, 문자열상수는 read only
pS는 시작주소를 가리키는 포인터가 됨
문자열배열은 메모리상의 배열공간에 데이터를 채워넣은 형식
배열명은 포인터상수이므로 *Arr++와 같은 형태는 사용 불가, 배열은 read/write 가능
일반변수->일반배열
포인터변수->포인터배열
char* pst[3]; // 포인터배열, sizeof(pst) == 12byte
char* pas[3] = {"embedded", "study", "good"}; // 포인터 배열의 선언및 초기화
문자열 상수는 메모리에 올라가는순간 주소를 뜻하므로 위와 같이 초기화 가능
더블포인터는 싱글포인터의 주소를 저장하는 변수
int temp = 3;
int* pt = &temp; // &temp : 0x200
int** pd = &pt; // &pt : 0x500
18-06-18
문자열상수
char* pst = "embedded";
문자열 상수도 마지막에 널문자가 포함되어있음
pst에는 "embedded"라는 문자열 상수가 올라간 메모리 주소의 시작주소를 가짐
"embedded\0"라는 문자열상수가 0x500에 있으면, pst는 0x500을 담고있음
문자열 상수는 read only 데이터로 *(pst + 0) = 'k'와 같이 write가 불가능함
char* pst1 = "embedded";
char* pst2 = "study";
char* pst3 = "good";
위와 같은 문자열은 아래와 같은 배열로 선언할 수 있음
char* pArr[3] = {"embedded", "study", "good"};
캐릭터형 포인터배열이므로 크기는 12byte
이때, pArr는 함수 매개변수로 넘겨줄때 데이터형은 char**이 됨
pArr을 함수 매개변수로 넘겨주는 함수 원형 예시로 Display(char** pD)
더블포인터는 싱글포인터의 주소를 저장하는 용도
int* pA와 같이 선언되었을때, &pA의 주소를 담는 변수는 int** ppA가 되어야함
2차원배열
int arr_m[3][4] = {0};
arr_m의 주소가 0x100 일때,
'arr_m+1의 주소는 0x110 이고, arr_m + 2의 주소는 0x120이 됨
배열 이름에 수치를 더하면 주소값이 2차원 배열의 열의 byte 수 만큼 증가함
2차원 arr_m을 함수 매개변수로 넘겨줄때 int (*pA)[4] 이라고 설정하며 이는 배열 포인터가 됨
배열 포인터는 포인터 배열과 이름이 혼동될수있는데, 포인터 배열은 다수의 싱글포인터를 모아놓은 것
배열 포인터
int arr_m[3][100]; 일경우
함수 매개변수는 int (*pArr)[100] 이라고 설정해서 사용
함수 내부에서는 pArr[0] == arr_m[0] == pArr + 0, pArr[1] == arr_m[1] == pArr + 1, ...
과 같음
int (*pArr)[5] 라는 배열 포인터는 자료형이 int이고, 열의 길이가 5인 배열은 모두 대입가능함
예로 arr[2][5] arr[10][5]
함수 매개변수 설정 방식 정리
(1)
int Arr[3];
modify(Arr);
void modify(int* pA)
(2)
int* pArr[3]
modify(pArr);
void modify(int** ppArr)
(3)
int temp=3;
int* ptr = &temp;
modify(ptr);
modify(int* pD) // call by value
modify(&ptr);
modify(int** pD) // call by address
(4)
int arr[2][3];
modify(arr); // 2차원 배열
modify(int (*pA)[3])
modify(arr[0]) // 1차원 배열
modify(int* pA)
메모리영역
data 영역 : 전역변수, static 변수
heap 영역 : 동적할당
stack 영역 : 지역변수, 함수 매개변수
함수포인터
함수명은 함수의 시작주소
2차원 배열의 주소를 저장하는 배열 포인터가 있듯이
함수의 주소를 저장하는 함수 포인터가 존재함
함수 포인터의 형태 int (*pfn)(int a, int b)
함수의 주소를 저장하는 함수 포인터 pfn은 반환 자료형과 매개변수의 자료형이 동일할 경우,
어떠한 함수명의 주소를 저장할수 있음
int modify(double a, int b)
int (*pf)(double, int); // 함수 포인터 선언
pf = modify; // 함수명을 함수 포인터에 저장
pf(10.0, 2) // 함수 포인터로 함수 호출
void 포인터
자료형이 없는 포인터로 모든 변수의 주소값을 저장하는 포인터 변수
예시로,
int temp = 5;
char ch = 'a';
void* pv = &temp; // 가능
void* pv = &ch; // 가능
해당 주소에 값을 write하기 위해서는 강제 형변환을 통해 자료형을 지정해줌
*(int*)pv = 10;
*(char*)pv = 'b';
18-06-19
2차원 배열
int arr[2][3];
arr : 배열명(시작주소) => 자료형 : 배열포인터(int (*pA)[3])
arr[0] : 첫번째 행의 시작 주소 (== arr + 0) => 자료형 : 싱글 포인터(int* pA)
arr[1] : 두번째 행의 시작 주소 (== arr + 1)
배열 포인터
2차원 배열의 시작 주소를 저장하는 배열 포인터는
'int (*pA)[열개수]' 와 같은 형태로 선언하며, int *(pA)[3] = arr 로 대입이 가능
배열 포인터는 열개수가 동일해야함
2차원 배열을 함수 매개변수로 넘겨줄때는 int (*pA)[열개수] 의 배열 포인터 형태로 설정
함수 내부에서는 pA[a][b] == *(pA[a]+ b) == *(*(pA+a)+b) 형태로 2차원 배열 값에 접근이 가능함
함수 포인터
함수명(시작주소)를 저장하는 포인터
int (*pfn)(int a, int b) 와 같은 형태로 선언
반환 자료형과 매개변수의 자료형이 동일한 함수명(시작주소)를 담을 수 있음
void 포인터
자료형에 상관없이 주소값을 저장하는 포인터
void 포인터에 저장된 주소값은 사용시에 캐스팅하여 사용해야함
정적할당
1)컴파일 단계에서 메모리가 할당
2)일반 배열(int arr[5])을 예로 들수있음
3)접근속도가 빠름
4)코드 유지보수(데이터 추가/삭제)가 불편
5)메모리 낭비 및 오버플로우 발생 가능성이 높음
동적할당
1)run time(실행시간)에 heap 영역에 메모리 할당
2)링크드 리스트를 예로 들수있음
3)포인터로 연결되어있기때문에 접근속도가 느림
4)코드 유지보수(데이터 추가/삭제)가 쉬움
5)메모리 낭비가 없음
6)메모리 자동해제가 되지 않아 직접해제 해야함
동적할당함수
stdlib.h에 함수 원형 존재
함수 원형 => void* malloc(size_t size); // size_t : typedef unsinged int size_t
malloc 함수는 리턴타입이 void 포인터(할당된 메모리의 시작주소)이므로,
void* pv = malloc(40); // 40byte 할당, 힙영역 시작 메모리 주소 pv에 저장
형태로 사용하고, pv는 stack영역에 생성되며 지역변수, 반환된 주소는 heap 영역의 메모리 주소
heap영역에 할당된 40byte를 접근하는 변수는 따로 없으며 오로지 반환된 포인터로만 접근이 가능
강제형변환을 이용한 동적할당
int* pn = (int*)malloc(40); // *(pn+0)=4; 또는 pn[0] 와 같은 형태로 값 할당
char* pc = (char*)malloc(40);// *(pc+0)='a'; 또는 pc[0] 와 같은 형태로 값 할당
동적할당을 할때, 항상 반환된 주소값이 NULL인지 확인하는 부분이 필요함
예시 코드는 아래와 같음
int* pBuffer = (int*)malloc(sizeof(int)*nSize); // nSize는 개수
if (pBuffer == NULL)
{
printf("# 동적할당 실패!!\n");
exit(1); // 프로그램 종료 함수
}
동적할당해제함수
free(pc); // free(동적할당한 메모리 시작주소를 저장한 포인터변수)
free의 전달 매개변수는 할당된 주소를 담은 어떤 포인터 변수라도 상관없음
예를들어 특정 함수에서 동적할당을 한 이후에 반환을 동적할당된 주소를 하고나면
그 주소를 담은 포인터 변수를 이용해서 free하면됨
■malloc과 new의 차이
malloc을 이용한 동적할당은 단순히 메모리만 확보하는 기능만 있고, 생성자를 호출하는 기능이 전혀없음
객체를 동적할당할때는 new를 사용해야함 (new를 사용하여 동적할당할 경우, 생성자를 호출함)
구조체 (사용자 정의 자료형)
struct person
{
char name[30]; // 멤버변수
int age; // 멤버변수
};
여기서 'struct person'은 int, double과 같이 자료형으로 구조체(사용자 정의 자료형)라 부름
변수를 선언하는 방식은 아래와 같음
struct person p1;
typedef
자료형을 특정 문구로 정의해주는 키워드
ex) typedef unsigned char BYTE;
구조체에 적용을 시키면,
typedef struct person
{
char name[30]; // 멤버변수
int age; // 멤버변수
}PER;
와 같이 정의할 수 있음
여기서 이름을 제거해서 사용할수도있는데,
typedef struct
{
char name[30]; // 멤버변수
int age; // 멤버변수
}PER;
과 같이 구조체(자료형)을 정의할 수 있음
만약 자기참조포인터를 사용할 경우 아래와 같이 이름을 반드시 써야함
typedef struct person
{
char name[30]; // 멤버변수
int age; // 멤버변수
struct person* next; // 자기참조포인터
}PER;
구조체의 멤버변수들은 기본값으로 public으로 선언되있기에 외부에서 접근이 가능함
■구조체 멤버변수 직접접근 연산자 : dot(.)
PER P1; // 구조체 변수 선언
P1.age = 5; // 가능
P1.pay = 3.5; // 가능
P1.name = "Hong"; // 불가능 : 포인터상수(배열이름))에 주소값을 넣을 수 없음!!
strcpy(P1.name, "Hong"); // 가능
구조체 변수 선언과 동시 초기화를 할 수있음
PER P1 = {5, 3.5, "Hong"};
■구조체 멤버변수 간접접근 연산자 : arrow(->)
구조체의 시작 주소를 이용한 접근 연산자
PER p1 = {5, 3.5, "Hong"};
PER* pp1 = &p1;
pp1->age = 10;
pp1->apay = 45.5;
pp1->name = "Kim";
18-06-20
■구조체 이해
typedef struct
{
int age;
char name[30];
}PER;
typedef struct
{
int idx;
PER nop[30]; // 중첩 구조체
}Em_class;
1) Em_class st;
st.idx = 10; // idx에 접근
st.nop[0].age = 10; // age에 접근
2) Em_class* ptr = &st;
ptr->nop[1].age = 10; // age에 접근
참고로 직접/간접 접근에서 자료형은 가장오른쪽에 있는 변수의 자료형임
또한, 구조체를 함수의 매개변수로 넘겨줄때는 call by value 형식으로 할 경우
구조체 사이즈만큼 메모리공간이 생성되기때문에 비효율적이어서 call by address 형식을 사용함
■자기참조포인터
typedef struct person
{
char name[30];
int age;
char job[30];
struct person* next; // 자기 참조 포인터
}PER;
선형 링크드 리스트
자기 참조 포인터를 이용해서 구조체를 리스트로 연결하여 사용하는 구조
해당 노드의 자기 참조 포인터는 다음 노드의 주소를 가짐
마지막 노드의 구조체의 참조 포인터는 NULL이 되는것을 이용하여 데이터를 생성하고 삭제함
'교육 > 한컴MDS' 카테고리의 다른 글
[한컴MDS교육] 딥러닝 플랫폼 개발을 위한 임베디드SW 개발자 양성과정 (0) 2019.01.07 댓글