__________

Designing the Future with Circuits

반도체 회로설계 취준기

하만(Harman) 세미콘 반도체 설계 과정/임베디드 시스템을 위한 SW 구조설계

하만(Harman) 세미콘 아카데미 4일차 - SW 구조설계(함수 / 다차원 열 / 포인터)

semicon_circuitdesigner 2024. 3. 12. 12:35

[2024.03.12.화]


임베디드 시스템을 위한 SW 구조설계



C언어 함수


  • 함수 기본 형태
    • intmain(void)
      {
         ⑤함수 몸체
      }
      <명칭>
      ① 반환 형태
      ② 함수 이름
      ③ 매개 변수
      ④ 몸체 시작
      ⑤ 함수 몸체
      ⑥ 몸체 끝
  • 함수 호출 과정
    • 위에서 아래로 호출 흐름(시작: main 함수)
    • 코드 중 함수가 있으면 해당 함수 호출
    • 함수는 호출되기 전에 정의되어야 함
  • 다양한 형태의 함수
    • 반환값(return)이 없는 함수: 반환 형태가 void
      ex)
      void 함수이름 (int val)
      {
           실행내용
      }​
    • 매개변수가 없는 함수 ex) getch()
  • 함수의 선언
    • 이후에 정의될 함수에 대한 정보 제공
    • int Add(int a, int b); //=>Add 함수의 원형 선언
      
      int main()
      {
      	Add(3, 4);
          return 0;
      }
      
      int Add(int a, int b) //=>Add 함수의 정의
      {
      	int result = a+b;
          return result;
      }
  • 변수의 종류
    • 지역 변수: 중괄호 내에 선언되는 변수 -> 함수의 실행 중에만 존재. 함수 실행 종료 시 메모리 삭제
    • 전역 변수: 함수 내에 선언되지 않는 변수. 모든 함수에서 참조 가능
    • 정적 변수: 함수 내부, 외부 모두 선언 가능 -> 함수의 호출과 무관하게 항상 존재
    • 레지스터 변수: 선언에 많은 제한
  • 지역 변수
    • 지역변수는 변수가 선언된 함수 내에서만 접근 가능
    • while, for, it 등의 루프 내에서 선언되는 변수는 그 안에서만 존재
    • 매개변수도 지역 변수의 일종
  • 전역 변수
    • 프로그램 어디서나 접근 가능
    • 프로그램 종료시까지 존재
  • 재귀 함수
    • 자기 자신을 다시 호출하는 함수
    • "!(팩토리얼)" 계산 시 유용
    • 무한루프에 빠지지 않도록 탈출조건 설정 필요

1차원 배열 (3일차 내용 보충)


  • 배열 접근: 대괄호[ ]를 이용하여 인덱스 번호에 접근
    ex) array[0], array[1], ...
    -> 배열의 첫 요소는 0번째 요소(Zerobase)
  • 배열은 전역으로 선언 권장
  • 문자열은 문자의 배열/ 문자열의 끝 요소에 null 포함

<실습>

  1. ca배열에 Hello를 선언하고, 각 요소에 알파벳이 출력되며 마지막에는 null이 있음을 확인하는 프로그램 생성
    #include <stdio.h>
    #include <conio.h>
    
    int day03();
    void day04();	//함수 선언(함수의 프로토타입)
    
    int day03(void) {
    
    	char* str[] = { "Zero", "One", "Two", "Three", "Four",
    		"Five", "Six", "Seven", "Eight", "Nine" }; // 문자열 포인터(*) 배열 선언
    
    	printf("숫자키를 입력하면 해당 영단어를 표시합니다.(x는 종료)\n");
    	while (1) {
    		printf(">");
    		char c = getch();
    		printf("%c\n", c);
    		
    		if (c == 'x' || c == 'X') break;//c | 0x20 == 'x'로 조건 넣어도 작동. c | 0x20을 통해 입력 값을 소문자로 변환
    
    		int m = c - 0x30; //ASCII -> num값
    		printf("%c: %s\n", c, str[m]); //입력 값c: str의 m번째 값.
    
    		/*switch (c) {
    		case '1':	printf("One\n");	break;
    		case '2':	printf("Two\n");	break;
    		case '3':	printf("Three\n");	break;
    		case '4':	printf("Four\n");	break;
    		case '5':	printf("Five\n");	break;
    		case '6':	printf("Six\n");	break;
    		case '7':	printf("Seven\n");	break;
    		case '8':	printf("Eight\n");	break;
    		case '9':	printf("Nine\n");	break;
    		case '0':	printf("Zero\n");	break;
    		default:	printf("숫자가 아닙니다.\n");	break;
    		}*/
    		printf("\n");
    	}
    }
    
    void day04(void) {		//문자열과 문자배열
    	char ca[] = "Hello";	//ca[0] = 'H', ..., ca[4] = 'o', ca[5] = null(\0)
    	for (int i = 0; i < 6; i++) {
    		printf("ca[%d]: %c\n", i, ca[i]);
    	}
    }
    
    int main()
    {
    	//day03(); => 3일차 코드 실행x
    	day04();
    }​

    정상 작동 확인
  2. day04함수의 printf 내 함수 변경: printf("ca[%d]: %c (%02x)\n", i, ca[i], ca[i]);
    글자에 맞는 ASCII코드를 16진수로 함께 표현
  3. day04 코드 변경하여 2, 3번째 배열 요소 글자를 아스키코드 -1씩 변경
    void day04(void) {		//문자열과 문자배열
    	char ca[] = "Hello";	//ca[0] = 'H', ..., ca[4] = 'o', ca[5] = null(\0)
    	for (int i = 0; i < 6; i++) {
    		printf("ca[%d]: %c (%02x)\n", i, ca[i], ca[i]);
    	}
    
    	ca[2] -= 1;
    	ca[3] -= 1;
    
    	for (int i = 0; i < 6; i++) {
    		printf("ca[%d]: %c (%02x)\n", i, ca[i], ca[i]);
    	}
    }​

 

글자 변경 정상 출력

 


다차원 배열


  • 다차원 배열: 2차원 이상의 배열
  • 선언 예시
    int arr[10][10]: 10x10 2차원 배열
    int arr[5][5][5]: 5x5x5 3차원 배열
  • 2차원 배열의 접근방법

arr [y][x]

  • 2차원 배열 선언과 동시에 초기화
    • 행 단위로 모든 요소 초기화
      int arr[3][3]={
      	{1, 2, 3},
          {4, 5, 6},
          {7, 8, 9)
      };​

       
    • 행 단위로 일부 요소 초기화
      int arr[3][3]={
      	{1},
          {4, 5},
          {7, 8, 9}
      };​
    • 1차원 배열 형태 초기화
      int arr[3][3]={1, 2, 3, 4, 5, 6, 7};​
  • 초기화 리스트에 의한 배열 크기 결정
    • int arr[][] = {1, 2, 3, 4, 5, 6, 7, 8}; => ERROR!
    • int arr[][4] = {1, 2, 3, 4, 5, 6, 7, 8}; => OK
    • int arr[][2] = {1, 2, 3, 4, 5, 6, 7, 8}; => OK

다차원 배열과 포인터


    • 포인터(*)
      • 주소를 나타내는 변수
      • 배열을 인수로 넘길 때 사용
      • 연산이 가능 -> 자료형 존재
    • int 배열: 하나의 데이터마다 4byte 메모리 필요
    • Char 배열: 하나의 데이터마다 1byte 메모리 필요
    • =>int포인터와 char포인터는 연산결과가 다름
    • 대괄호 내에 주소 표현 -> ca + i 대입( i = 0~6)
      #include <stdio.h>
      #include <conio.h>
      
      int day03();
      void day04();	//함수 선언(함수의 프로토타입)
      
      void day04(void) {		//문자열과 문자배열
      	char ca[] = "Hello";	//ca[0] = 'H', ..., ca[4] = 'o', ca[5] = null(\0)
      	for (int i = 0; i < 6; i++) {
      		printf("ca[%d]: %c (%02x) [%08x]\n", i, ca[i], ca[i], ca + i); //%02x: 2바이트씩 16진수로, [%08x]에는 주소 표현(ca + i)
      	}
      
      	int ia[] = { 10, 20, 30, 40, 50 };
      	for (int i = 0; i < 6; i++) {
      		printf("ia[%d]: %d (%08x) [%08x] \n", i, ia[i], ia[i], ia + i); //%08x: 8바이트씩 16진수로
      	}
      }
      
      int main()
      {
      	//day03();
      	day04();
      }​

      ia의 주소값이 4씩 증가함을 확인(포인터 타입이 4byte를 사용하는 int이기 때문)
  • 배열 요소 접근
    • a[3][2]={10, 20, 30, 40, 50, 60}; 인 배열에 대해
      #include <stdio.h>
      #include <conio.h>
      
      int day03();
      void day04();	//함수 선언(함수의 프로토타입)
      
      void day04(void) {		//문자열과 문자배열
      	char ca[] = "Hello";	//ca[0] = 'H', ..., ca[4] = 'o', ca[5] = null(\0)
      
      	int ia2[3][2] = { 10, 20, 30, 40, 50, 60 };
      	for (int y = 0; y < 3; y++) {
      		for (int x = 0; x < 2; x++) {
      			printf("ia2[%d][%d]: %d(해당 위치의 데이터) [%08x](해당 위치의 주소) \n", y, x, ia2[y][x], ia2+y); //%08x: 8바이트씩 16진수로, [%08x]에는 주소 표현(ia + i)
      		}
      	}
      }​
      주소값이 8바이트씩 증가
    • 배열이 [2][3]이라면 y가 증가할 때 x가 3개 포함되므로 4byte*3=12byte씩 증가
    • 마지막 ia2+y에서 ia2는 배열 명 + y해줄 때 y가 1 증가할 때 2개의 int(x가 2개)가 포함. 따라서 8바이트씩 증가
  • 포인터 선언
    • int arr[2][4]; => 포인터 선언: int (*pArr)[4];
      int: int형 변수
      *pArr: 포인터
      [4]: 4칸씩 건너뜀
  • char (*pArr) [4] vs. char* pArr[4]
    • char* pArr[4]:  포인터 배열 =>  배열 요소가 모두 포인터이다.
      위 코드는 포인터의 배열을 생성
    • char (*pArr) [4]: 배열 포인터 => 2차원 배열을 인수로 전달하므로 괄호 사용
      위 코드는 배열의 타입을 가리키는 하나의 포인터

포인터의 이해(강의노트 5강)


  • 포인터
    • 포인터 ≠ 주소, 포인터≒변수
    • 포인터: 메모리의 주소값을 저장하는 변수(=값이 저장된 메모리의 주소값을 가리키는 변수)
    • 각각의 메모리 공간은 Address Bus 에 의해 연결
    • 컴파일러: 변수에 할당된 주소에 값 저장
  • 주소값이 0x1000, 0x1001, 0x1002, ...이 있을 때, 아래 코드에서
    int main(void)
    {
    	char c='a';
        int n=7;
        double d=3.14;
        ...
    }​
    • c='a' 변수는 0x1000에 1byte를 사용하여 저장
    • n=7 변수는 0x1001~0x1004에 4byte를 사용하여 저장
    • d=3.14 변수는 0x1005~0x100c에 8byte를 사용하여 저장
  • 포인터 선언
    • *를 사용하여 선언(데이터 타입에 *를 붙이고, 그 후 포인터 이름 선언)
      int main(void)
      {
      	int* a;		//a이름의 int형 포인터
          char* b;	//b이름의 char형 포인터
          double* c;	//c이름의 double형 포인터
          ...
      }
  • 주소 관련 연산자
    • &: 변수의 주소 값 반환(scanf 실습에서 사용)
      ex) printf(&a); => a변수의 주소값 출력
    • *: 포인터가 가리키는 메모리 참조
      해당 주소의 값을 가져올 때 앞에 *를 붙임
    • int main(void)
      {
      	int a=2005;
      	int *pA=&a;
      	printf(“%d”, a); //직접 접근
      	printf(“%d”, *pA); // 간접 접근
      	. . . . .
      }
      • int *pA=&a;  => a의 주소를 pA 라는 이름의 integer pointer에 대입
      • printf(“%d”, *pA); => *pA: "a의 주소를 갖는 pA 포인터의 값을 가져와라" => 2005를 가져와서 출력
      • 작동 원리:
        a=2005라는 값이 메모리의 주소 0x1000~0x1003에 저장 => pA에는 0x1000이 저장 => *pA는 0x1000에 저장된 값 2005를 가져오는 방식
      • *pA++; => a++와 같은 의미를 지님

포인터 실습


1. buf가 메모리 공간 100에 생성

2. 그 메모리 공간 중 입력을 시작할 주소를 설정(scanf와 addr변수 사용): 기존의 메모리 공간에 입력한 값만큼 이동한 공간에서 시작

3. 해당 위치에 들어갈 문자열을 입력

4. for문에서 문자열을 바이트별로 출력하여 주소와 바이트 출력

 

void Dump (,) 함수설명

주소 시작지점부터, 1000부터 시작한다고 가정하면 1000, 1001, 1002, ..., 100F까지 출력할 것. 16개 출력되었으면 줄바꿈 -> 주소가 1010, 1011, ...로 변경 => 따라서 줄바꿈을 할 때 i가 16배수일 것 => i가 16 배수일 때 줄바꿈 실행하라

#include <stdio.h>
#include <conio.h>

void day04_02();
void Dump(char* p, int len);	//메모리 공간 출력용 범용 함수
void Copy(char* p1, char* p2);

void day04_02() {
	char buf[100];			//메모리 공간에 방 100개 만들기(안전한 메모리 공간 확보)
	char* pBuf;				//안전 메모리 공간중의 출력 위치 설정
	unsigned int addr;				//메모리 공간 중 출력 위치 지정을 위한 입력 변수(주소)
	char kBuf[100];			//출력할 문자열을 scanf로 입력받기 위한 공간을 미리 확보

	printf("buf(안전공간)의 주소는 %d[%08x]입니다.\n", (unsigned int) buf);	//%d에서는 10진수로 표시되므로 [  ]안에 참고용으로 16진수 출력
	printf("입력을 시작할 주소를 입력하세요: ");//buf이름의 메모리 공간 중 값이 할당할 시작 주소 입력(위의 안전공간 주소 참고)
	scanf("%d", &addr);							//기존 주소에 addr만큼 더해진 주소로 입력 값 저장

	pBuf = buf + addr;							//위에서 초기화한 addr의 값을 pBuf에 저장

	printf("문자열을 입력하세요: ");
	scanf("%s", kBuf);				//출력할 문자열 입력
	Copy(pBuf, kBuf);				//strcpy(복사할 위치, 복사할 내용)

	Dump(buf, 100);					//길이는 kBuf에서 설정한 100
}

void Copy(char* p1, char* p2) {
	while (*p2) *p1++ = *p2++, *p1 = 0;;
	/*
	조건식:p2의 값(p2의 값이 null이 아니라면, 계속 수행)
	수행 내용: p1의 값에 p2값을 한글자씩 복사 => p2값의 마지막인 null값에 도달하면 p1값의 마지막에 null을 입력하며 완성
	*/
}


void Dump(char* p, int len) {							//p는 char 포인터(p는 주소값을 말함)
	for (int i = 0; i < len; i++) {						//함수 입력 시 입력받는 길이 len만큼만 반복
		if (i % 16 == 0)		printf("\n%08x", p);	//반복실행을 수행하기 위해 i가 16의 배수일 때 실행하도록 설정 -> 이 때 주소(p)를 출력
		if (i % 8 == 0)			printf("    ");			//8개를 출력할 때 공백 추가
		printf("%02x   ", (unsigned char)*p++);			//*의 의미: 포인터에서 p주소에 있는 값, 주소를 한칸씩 옮기며 출력
	}
}


int main()
{
	//day03();
	//day04();
	day04_02();
}

실행 화면


포인터와 배열


  • 배열 이름: 첫 번재 요소의 주소 값
  • 포인터 vs. 배열 이름
    • 둘 다 이름이 존재하고 메모리의 주소 표현
    • 포인터는 변수 / 배열 이름은 상수
    • (example)
      int main(void)
      {
      	int a[5] = {0,1, 2, 3, 4};
          int b = 10;
          a =& b;
      }​
       위 코드에서는 a가 상수이므로 오류 발생. a가 변수여야 성립