버퍼

C 표준입출력 라이브러리에서는 내부적으로 버퍼를 도입하여 입출력을 효율적으로 처리합니다. printf나 scanf 등의 라이브러리 함수는 결국 입력과 출력을 read, write를 통해서 이루어집니다. 버퍼라는 공간을 두는 이유는 내부적으로 write, read를 적시에 최소한으로만 호출하기 위한 것이 목적입니다. 왜 그런 뻘짓을 하느냐구요? CPU를 많이 사용하지 않기 위해서입니다. 한 글자 씩 계속 입력을 받거나 출력을 하면 그만큼 read, write 콜이 잦아지는데 이러면 CPU에 부담이가 성능에 안좋을 영향을 끼치게 되는 겁니다. 버퍼를 사용하는 것은 라이브러리 함수인면에서 응용 프로그램에서는 신경쓰지 않아도 되지만, 버퍼의 처리 방식을 모르게 되면 낭패를 봅니다.

어떻게 버퍼를 이용하는 것을 버퍼링이라고 하고  따라서 세가지 버퍼링 방식이 있습니다. 

전체 버퍼링(Full buffering)

이러한 버퍼링은 내부 버퍼에 데이터가 꽉 차게 되면 그제서야 입출력이 되는 방식입니다. 그러니까 버퍼가 전부 차기 전에는 이 데이터를 가지고만 있고 입출력은 하지 않는 것이죠. 위에서 얘기했듯이 이러한 버퍼링의 목적은 read, write를 최소한으로 사용하기 위함입니다. 그래서 버퍼가 전부 찰 때까지 기다리고 있죠. 이 때 "버퍼의 크기가 크면 무조건 좋은 거 아닌가?" 라는 물음을 던질 수 있는데, 정도라는 것이 있듯 최적의 버퍼 크기가 정해져있습니다. 이것을 표준 입출력 라이브러리가 정해줍니다. 우리는 개-꿀만 빨면 됩니다.

보통 파일을 디스크로부터 읽을 때의 버퍼링 방식입니다. 

아래와 같은 경우가 전체 버퍼링의 예를 보여줍니다. 붉은 사각형은 비어있는 데이터를 의미하며 파란 사각형은 채워진 데이터를 의미합니다. 현재는 버퍼에 2바이트의 데이터가 모자라서 파일에 기록하지 않고 있습니다. 이때 2바이트가 채워지고 있는 모습입니다. 

전체 버퍼링1

이 때 완전히 버퍼가 채워지면 그제서야 데이터를 한꺼번에 파일로 전송하게 됩니다. 

 

줄 단위 버퍼링(Line buffring)

scanf나 fgets, fgetc 등의 표준 입력 함수나 printf, fputs, putc 등의 함수를 이용한 표준 출력을 사용할 때 이러한 줄 단위 버퍼링이 적용됩니다. 줄 단위 버퍼링은 새 줄 문자('\n')가 나올 때 까지 입력이나 출력을 하는 것입니다. 또한 버퍼가 차게 되면 입출력을 진행합니다. 이 때 버퍼의 크기는 보통 전체 버퍼링의 버퍼 크기보다 작습니다.

아래와 같은 경우가 줄 단위 버퍼링을 보여줍니다. 아직 데이터가 전부 채워지지 않았으며 이 때 개행 문자인 '\n'이 입력이 되고 있는 상황입니다. 

개행 문자를 만나면 버퍼가 채워져있지 않음에도 입출력을 진행하게 됩니다. 

 

비 버퍼링(Unbuffered)

버퍼링은 하지 않는 방식입니다. 왜요? 급하기 때문입니다. 여러분도 급똥이면 장사없듯이 프로그램도 급하면 장사없습니다. 언제가 급할까요? 에러를 출력할때가 그런 상황입니다. 지체없이 에러를 해결해야할 상황이 생기기 때문이지요. 

버퍼링 정보 가져오기

그렇다면 보통의 표준 입력, 표준 출력, 표준 에러나 파일에 대한 스트림은 어떤 버퍼링 방식을 갖고 버퍼 크기는 어떻게 결정이 될까요? 아래의 코드는 상황에 따른 입,출력 버퍼에 대한 정보를 표시해주는 코드입니다.

//buffer_info.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#ifdef __GLIBC__
#define _IO_UNBUFFERED 0x0002
#define _IO_LINE_BUF 0x0200
#endif

int main(int argc, char *argv[]){
        FILE *fp;

        char buf[32] = {0, };

        if(argc =! 2){
                printf("Usage : %s stdin | stdout | stderr | file_name\n",
                                argv[0]);
                return 1;
        }


        if(!strcmp(argv[1], "stdin")){
                fp = stdin;
                fgets(buf, sizeof(buf), fp);
        } else if(!strcmp(argv[1], "stdout")){
                fp = stdout;
                printf("stdout\n");
        } else if(!strcmp(argv[1], "stderr")){
                fp = stderr;
                fprintf(fp, "stderr\n");
        } else {
                fp = fopen(argv[1], "r");

                if(fp == NULL){
                        printf("fopen error\n");
                        return 1;
                }

                while(fgets(buf, sizeof(buf), fp) != NULL);
        }

        if(fp->_flags & _IO_UNBUFFERED)
                printf("비버퍼링\n");
        else if(fp->_flags & _IO_LINE_BUF)
                printf("줄단위 버퍼링\n");
        else
                printf("전체 버퍼링\n");

        printf(" 버퍼 사이즈 : %ld\n", fp->_IO_buf_end - fp->_IO_buf_base);

        fclose(fp);
}

 

# ./a.out stdin
hello
줄단위 버퍼링
 버퍼 사이즈 : 1024
# ./a.out stdout
stdout
줄단위 버퍼링
 버퍼 사이즈 : 1024
# ./a.out stderr
stderr
비버퍼링
 버퍼 사이즈 : 1
# ./a.out /etc/group
전체 버퍼링
 버퍼 사이즈 : 4096

 

단순히 stdin, stdout, stderr에 대해서 fgets나 printf를 한번 호출하지 않고서 fp->_flags를 들여다보면 다른 결과가 나올 수 있습니다. 예를 들면 아래와 같이 fgets를 주석 처리하고 실행해보시면 다른 결과를 보실 수 있을 겁니다. 

    if(!strcmp(argv[1], "stdin")){
            fp = stdin;
            //fgets(buf, sizeof(buf), fp);

아래의 결과가 위처럼 fgets를 주석처리한 예인데, 결과가 다르죠?

# ./a.out stdin
전체 버퍼링
 버퍼 사이즈 : 0

이러한 결과를 통해서 알 수 있는 것은 스트림을 열었다고 해서 그 버퍼링이 설정된다는 것이 아니라, read, write를 하는 함수들이 버퍼링을 결정해준다는 사실입니다. 

 

버퍼링 설정 

1. setbuf

#include <stdio.h>

void setbuf(FILE *stream, char *buf);

setbuf 함수를 이용해서 버퍼링 방식을 설정할 수 있습니다. 대신 시스템이 정해준 버퍼인 BUFSIZ로만 사용이 가능합니다. 반대로 버퍼링을 끌 수도 있습니다.

버퍼를 설정하려면 buf[BUFSIZ]의 버퍼를 *buf인자에 전달해야하고, 버퍼를 끄려면 NULL을 전달하면 됩니다.

아래의 예를 한번 볼까요?

//setbuf.c
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>

//버퍼링을 키거나 끄는 쓰레드 
void* buf_control(void *arg){
        char buf[BUFSIZ] = {0,};
        int on;
        while(1){
                scanf("%d", &on);
                switch(on){
                        case 0: //OFF
                                setbuf(stdout, NULL);
                                break;
                        case 1: //ON
                                setbuf(stdout, buf);
                                break;
                }
        }

}

//1초마다 "A"를 계속 찍는 쓰레드
void* print_line(void *arg){
        while(1){
                printf("A");
                sleep(1);
        }
}
int main(int argc, char *argv[]){
        pthread_t tid1, tid2;

        printf("[0] 버퍼 동작 X\t [1] 버퍼 동작 O\n");

        pthread_create(&tid1, NULL, buf_control, NULL);
        sleep(1);
        pthread_create(&tid2, NULL, print_line, NULL);

        pthread_join(tid1, NULL);
        pthread_join(tid2, NULL);
}
# ./a.out 
[0] 버퍼 동작 X  [1] 버퍼 동작 O
0
AAAAAAAAAAAAAAA1
0
AAAAAAAAAAAA1A
0
AAAAA1
0
AAA1
0
AAAAA^C

 

pthread의 개념을 몰라도 좋습니다. 단순히 buf_control이라는 함수, print_line이라는 함수가 동시에 수행되는 것만 아시면 됩니다. 

buf_control이라는 함수에서는 버퍼링을 끄거나 키거나 할 수 있는데, 1은 버퍼링을 키는 동작, 0은 버퍼링을 끄는 동작이라는 것을 볼 수 있을 겁니다. 

print_line함수는 한 글자씩 1초마다 printf를 통해서 출력을 해주는 함수입니다. 단 줄바꿈(\n)은 하지 않죠. printf는 디폴트 동작으로는 줄단위 버퍼링 방식을 사용하는 것을 위에서 확인했었죠?? 그래서 버퍼링을 설정하게 되면 줄바꿈이 나오지 않거나 버퍼 크기인 1024바이트가 채워지지 않으면 출력을 하지 않습니다.

2. setvbuf

#include <stdio.h>
int setvbuf(FILE *stream, char *buf, int mode, size_t size);

세 가지의 버퍼링 방식을 설정할 수 있습니다. stream에 대해서 size만큼의 buf를 버퍼링합니다. 이때 mode는 비버퍼링(unbuffered), 줄 단위 버퍼링(line buffering), 전체 버퍼링(full buffering)을 설정할 때 쓰입니다. mode에 대한 설명은 아래를 참고하세요. 

mode 설명 
_IONBF 비버퍼링 모드 
_IOLBF 줄 단위 버퍼링 모드
_IOFBF 전체 버퍼링(블록 버퍼링) 모드

setvbuf는 성공시 0, 실패시 0이 아닌 값을 설정하여 돌려줍니다.

확실히 setbuf 함수보다는 보다 세세한 설정이 가능하죠? 그렇다면 setvbuf함수를 통해서 버퍼링을 설정하는 예를 볼까요? 아래는 stdout을 줄 단위 버퍼링이 아닌 4바이트의 전체 버퍼링으로 바꾼 하나의 예입니다.

//setvbuf.c
#include <stdio.h>
#include <unistd.h>

#ifdef __GLIBC__
#define _IO_UNBUFFERED 0x0002
#define _IO_LINE_BUF 0x0200
#endif

#define BUF_SIZE 4

int main(){

        char buf[BUF_SIZE] = {0,};
        FILE *fp = stdout;

        if(setvbuf(fp, buf, _IOFBF, BUF_SIZE) != 0){
                printf("setvbuf _IOLBF error \n");
                return 1;
        }

        if(fp->_flags & _IO_UNBUFFERED) printf("비버퍼링\n");
        else if(fp->_flags & _IO_LINE_BUF) printf("줄단위 버퍼링\n");
        else printf("전체 버퍼링\n");

        while(1){
                printf("A");
                sleep(1);
        }

}

실행해보면 줄 단위 버퍼링이 아닌 전체 버퍼링으로 설정된 것을 볼 수 있습니다. 그리고 4초마다 버퍼가 꽉 채워지기 때문에 출력이 되는 동작을 확인할 수 있네요.

# ./a.out 
전체 버퍼링
AAAAAAAAAAA^C
반응형
블로그 이미지

REAKWON

와나진짜

,

strftime

strftime은 시간 커스텀할 수 있게 만들어준 문자열 서식화 함수인데요. 문자열에 날짜에 대한 형식을 지정하면 문자열에 시간에 대한 정보가 들어옵니다. strftime의 원형은 다음과 같습니다.

#include <time.h>

size_t strftime(char *s, size_t max, const char *format, const struct tm *tm);

 

time.h를 include해야합니다. 

s : 담을 문자열입니다.

max : 문자열의 최대 길이를 지정합니다.

format : 여기에 서식화할 format문자가 들어갑니다. 

tm : 시간정보를 담은 tm구조체가 들어와야합니다. tm구초제는 아래와 같이 정의되어있습니다.

struct tm {
   int tm_sec;    /* 초 (0-60) */
   int tm_min;    /* 분 (0-59) */
   int tm_hour;   /* 시간 (0-23) */
   int tm_mday;   /* 월의 일 (1-31) */
   int tm_mon;    /* 월(0부터 시작) (0-11) */
   int tm_year;   /* 년 */
   int tm_wday;   /* 주의 일 (0-6, Sunday = 0) */
   int tm_yday;   /* 년의 일 (0-365, 1 Jan = 0) */
   int tm_isdst;  /* Daylight saving time */
};

 

반환 : 성공시 문자열의 크기, 실패시 0이 반환됩니다.

 

format의 문자는 매우 다양합니다. 아래의 표로 정리하긴했지만, 이것보다 훨씬 많습니다. 여기에 나오지 않는 format문자는 인터넷 서치하시거나 매뉴얼 페이지를 보시기바랍니다.

format 문자 설명
%a 짧은 요일 이름 Thu
%A 긴 요일 이름 Thursday
%b, %h 짧은 달 이름 Jan
%B 긴 달 이름 January
%c 날짜 + 시간 Tue Apr  5 15:35:05 2022
%C 두자리 연도(00-99) 20
%d 그달의 일(01-31) 30
%D 날짜(MM/DD/YY) 04/05/22
%e 그 달의 일(1-31) 10
%H 그 날의 시(24시간) 23
%I 그 날의 시(12시간) 11
%m 달(01-12) 02
%M 분(00-59) 55
%p 오전/오후 PM
%S 초(00-60) 30
%T %H:%M:%S 12:30:24
%R %H:%M 12:30
%r 12시간 형식의 로컬 시간 05:40:12 PM

 

아래는 이러한 strftime함수를 사용한 예입니다.

#include <time.h>
#include <stdio.h>

int main() {
    time_t now;
    struct tm* t;
    char buf[128] = { 0, };

    time(&now);
    t = localtime(&now);
    if (strftime(buf, sizeof(buf), "current - %c", t) == 0) {
        printf("strftime 실패\n");
        return -1;
    }
    printf("%s\n", buf);

    return 0;
}

 

 

오늘은 4월 5일로 나무 심으러 가도록 합시다.

strptime

strptime함수도 존재하는데, strftime의 역이라고 생각하시면 됩니다. formatting된 문자열에서 시간 구조체인 tm을 얻을 때 사용합니다.

char *strptime(const char *s, const char *format, struct tm *tm);

 

파라미터 설명은 넘어가도록 하고 사용 예를 보도록 하겠습니다.

#include <stdio.h>
#include <string.h>
#include <time.h>

int main(void){
        struct tm tm;
        char buf[255];

        memset(&tm, 0, sizeof(struct tm));
        strptime("2020-09-21 07:09:30", "%Y-%m-%d %H:%M:%S", &tm);
        printf("%d/%d/%d %d:%d:%d\n",tm.tm_year+1900, tm.tm_mon+1, tm.tm_mday,
                                        tm.tm_hour, tm.tm_min, tm.tm_sec);
}

 

시간을 나타내는 문자열 서식화 함수 strftime과 strptime을 알아보았습니다.

반응형
블로그 이미지

REAKWON

와나진짜

,

반복문 for

C언어에는 여러 반복문이 존재합니다. for문, while문, do ~ while문이 있지요. 오늘은 가장 흔하게 쓰이는 for문에 대하여 이야기해보도록 하겠습니다. 

for문에 대한 형식은 일반적으로 아래와 같은 형태입니다.

1. 초기화 식이 먼저 수행됩니다. 이는 for문 최초 1회만 수행됩니다.

2. 이후 조건식을 보고 true이며 for문 내부에 있는 코드를 수행하고, false이면 for문을 멈추고 빠져나옵니다.

3. 조건식이 참일 경우 수행되는 내부 부분입니다.

4. 이후 증감식을 수행한 후 다시 2의 조건식에 맞는지 확인합니다. 

이후 2 ~ 4의 반복입니다.

 

예 ) 1~10까지의 합

이러한 기본 형식을 지킨 1부터 10까지 모두 더한 수를 구한 예제입니다.

#include <stdio.h>

void main() {
	int i;
	int sum = 0;
	for (i = 1; i <=10; i++) {
		sum += i;
	}
	printf("1부터 10까지의 합 : %d\n", sum);
}

 

중첩 for문 

for문안에 for문이 등장하여 중복적으로 반복문을 수행할 수도 있습니다. 이를 중첩 for문, 혹은 2중 for문이라고 합니다. 원리는 간단합니다. 수행 부분이 내부 for문으로 바뀌는 거거든요. 아래는 이와 관련된 구구단의 예제입니다.

int i, j;
for (int i = 2; i <= 9; i++) {
	for (int j = 1; j <= 9; j++) {
		printf("%d * %d = %d\n", i, j, i * j);
	}
	printf("\n");
}

 

for문의 제어 - continue, break 

상황에 따라서 이번 수행부분은 건너뛰거나, 아예 for문을 멈춰야하는 상황이 발생할 수 있습니다. 이럴 경우 쓰이는 키워드가 if와 함께 continue와 break입니다.

continue : 이후 for문의 실행부분을 건너뜁니다.

break : break가 있는 for문을 종료시킵니다. 2중 for문의 경우 break가 등장하는 for문을 멈춰버립니다.

예 ) 1~10까지 짝수만 출력

for (int i = 1; i<=10; i++) {
	if (i % 2 == 1) continue;
	printf("%d \n", i);
}

 

예) 특정수와 일치하는 배열의 index 찾기

void main() {
	
	int numbers[] = { 5,4,2,1,9,11 };
	int found_index = -1;
	for (int i = 1; i<sizeof(numbers); i++) {
		if (numbers[i] == 9) {
			found_index = i;
			break;
		}
	}
	if (found_index != -1) {
		printf("9의 배열 index:%d\n", found_index);
	}
	else {
		printf("9를 찾을 수 없음\n");
	}
}

 

for문의 다양한 사용법 

for문은 일반적으로 이런식으로 사용한다는 것을 보여드렸습니다. 초기화식, 조건식, 증감식은 모두 생략이 가능합니다. 아래와 같이 다양하게 사용할 수 있습니다.

for(;;) // 무한 루프
for(i=1;;) //무한 루프
for(;i<5;i++) //초기화 구문 생략
for(;;i++)	//i가 1씩 증가하는 무한루프
for(i=1,j=1;;i++,j++)	//두가지 변수를 ,로 동시에 증감이 가능

 

for문 활용 - 별 출력

for문의 가장 대표적인 예제로는 별찍기가 있는데요. 한번 풀어보도록 합시다. 처음 몇개는 쉬우니까 우선 풀이는 생략하고 코드만 올리도록 하겠습니다.

1. 

*
**
***
****

아래는 코드입니다.

void main() {
	printf("*\n");
	printf("**\n");
	printf("***\n");
	printf("****\n");
}

장난이구요. 

void main() {
	int i, j;
	for (i = 1; i <= 4; i++) {
		for (j = 1; j <= i; j++)
			printf("*");
		printf("\n");
	}
}

 

2.

****
***
**
*
void main() {
	int i,j;
	for (i = 4; i > 0; i--) {
		for (j = 1; j <= i; j++)
			printf("*");
		printf("\n");
	}
}

 

3. 

   * 
  ***
 *****
*******

여기서부터 좀 많이 헤메실수 있어서 풀이방법도 기재해드립니다. 우선 코드분석해보시고 풀이를 봐주세요.

#include <stdio.h>

void main() {
	int i, j, height;
	scanf("%d", &height);
	for (i = 1; i <= height; i++) {
		for (j = 1; j <= height - i; j++) //공백 먼저 출력
			printf(" ");
		
		for (j = 1; j <= i * 2 - 1; j++)  //*출력
			printf("*");
		printf("\n");
	}
}

 

우선 i는 *과 공백을 포함한 한 줄을 의미합니다. 공백과 별을 어떻게 출력하는지는 아래와 같은 규칙을 갖게 됩니다. 아래의 그림은 높이가 4인 삼각형 모양의 별을 나타내었습니다.

i가 1일 경우 ) 우선 공백이 3개 출력되고, 별이 1개 출력이 됩니다.

i가 2일 경우 ) 공백이 2개 출력되고, 별이 3개 출력됩니다.

i가 3일 경우 ) 공백이 1개, 별이 5개 출력됩니다.

i가 4일 경우 ) 공백이 0개, 별이 7개 출력됩니다.

자, 이제 일반화하게 되면, 아래와 같은 규칙을 얻게 되지요.

공백 : height - i 까지 출력

별 : i*2 -1까지 출력

그래서 위의 코드와 같이 쓸 수 있습니다.

4.

*******
 *****
  ***
   *

이거는 i를 처음부터 height값으로 놓고 --i로 거꾸로 돌리면 됩니다.

void main() {
	int i, j, height;
	scanf("%d", &height);
	for (i = height; i >= 0; i--) {
		for (j = 1; j <= height - i; j++) //공백 먼저 출력
			printf(" ");
		
		for (j = 1; j <= i * 2 - 1; j++)  //*출력
			printf("*");
		printf("\n");
	}
}

 

4.

   *
  ***
 *****
*******
 *****
  ***
   *

 

위의 두 코드를 합치면 되는데, 중간에 같은줄이 두개 나오기 때문에 밑에는 height -1로 하여 한번 빼줍시다.

	for (i = 1; i <= height; i++) {
		for (j = 1; j <= height - i; j++) //공백 먼저 출력
			printf(" ");

		for (j = 1; j <= i * 2 - 1; j++)  //*출력
			printf("*");
		printf("\n");
	}

	for (i = height-1; i >= 0; i--) {
		for (j = 1; j <= height - i; j++) //공백 먼저 출력
			printf(" ");

		for (j = 1; j <= i * 2 - 1; j++)  //*출력
			printf("*");
		printf("\n");
	}

 

이상으로 별까지 출력하는 코드로 for문을 활용해보았습니다. for문은 배열과 같이 많이 쓰이기 때문에 반드시 알아야하는 반복문입니다.

반응형
블로그 이미지

REAKWON

와나진짜

,

switch ~ case

switch ~ case 문법은 if문과 비슷하지만 약간은 다른 문법인데요. if 문은 괄호안에 조건식에 따라서 비교를 하고 조건식이 꽤나 복잡해질 수 있습니다. 이럴때 가독성이 조금 떨어지게 되지요. 하지만 switch case 문은 보다 명료하고 간단하게 조건에 따라 실행하는 case들이 나눠지기 때문에 가독성이 뛰어나다는 장점이 있죠. 이제 switch ~ case를 어떻게 사용하는가, 그리고 흔하게 할 수 있는 실수들은 무엇인가에 대해서 설명해보려고 합니다. 

1. 기본 사용법

switch case의 기본 템플릿은 아래와 같습니다.

switch : switch안에는 정수형으로 이 정수의 데이터를 가지고 case 옆의 데이터와 비교하게 됩니다.

case n : 실제 수행 부분입니다. swtich에 전달한 정수형 인자와 일치하면 그 case문을 실행하게 되는거죠.

default : case에 모두 포함되지 않을 경우 default 안쪽의 코드가 실행됩니다. default는 생략할 수도 있습니다.

 

switch(정수형){
case 1: 
//...//
break;
case 2:
//...//
break;
case 3:
//...//
break;
//...//
default:
//...//
}

 

아래는 switch ~ case를 이용한 간단한 예제입니다. 

#include <stdio.h>

void create_new_game() {
	printf("새로운 게임 만들기\n");
}

void continue_game() {
	printf("지난 게임 계속\n");
}
void game_setting() {
	printf("게임 설정\n");
}
void exit_game() {
	printf("게임 종료\n");
}
void main() {

	int selection;
	printf("1. 새게임\n");
	printf("2. 계속\n");
	printf("3. 설정\n");
	printf("4. 종료\n");

	scanf("%d", &selection);
	switch (selection) {
	case 1: 
		create_new_game();
		break;
	case 2:
		continue_game();
		break;
	case 3:
		game_setting();
		break;
	case 4:
		exit_game();
		break;
	default:
		printf("없는 옵션입니다\n");
	}
}

 

switch안에는 정수형 인자를 넣어야한다고는 했지만, 문자도 가능합니다. 문자도 내부적으로 정수로 처리하게 되니까요. 

void main() {

	char selection;
	printf("a. 새게임\n");
	printf("b. 계속\n");
	printf("c. 설정\n");
	printf("d. 종료\n");

	scanf("%c", &selection);
	switch (selection) {
	case 'a': 
		create_new_game();
		break;
	case 'b':
		continue_game();
		break;
	case 'c':
		game_setting();
		break;
	case 'd':
		exit_game();
		break;
	default:
		printf("없는 옵션입니다\n");
	}
}

 

 

단, 문자열의 경우에는 사용할 수 없습니다.

 

2. break가 없는 경우

case 마지막에 break를 항상 입력하는 것을 볼 수가 있죠? 왜 그렇게 될까요? 위 코드에서 break만 없애서 실행을 해보도록 하겠습니다.

void main() {

	char selection;
	printf("a. 새게임\n");
	printf("b. 계속\n");
	printf("c. 설정\n");
	printf("d. 종료\n");

	scanf("%c", &selection);
	switch (selection) {
	case 'a': 
		create_new_game();
	case 'b':
		continue_game();
		
	case 'c':
		game_setting();
	case 'd':
		exit_game();
	default:
		printf("없는 옵션입니다\n");
	}
}

 

저는 b를 선택하였음에도 b 이후 모든 case가 동작하게 됩니다. break라는 것은 곧 멈추라는 의미로 break가 없으면 선택 case 이후의 모든 case를 수행하게 됩니다. 

 

3. case안에서 변수 선언

case안에서 특정 변수를 선언하고 코드를 짜보도록 합시다. 코드를 아래처럼 바꿔서 실행해봅시다.

#include <stdio.h>

void create_new_game(char *new_game_name) {
	printf("새로운 게임 만들기 :%s\n",new_game_name);
}
//... 생략 ...//
void main() {

	char selection;
	printf("a. 새게임\n");
	printf("b. 계속\n");
	printf("c. 설정\n");
	printf("d. 종료\n");

	scanf("%c", &selection);
	switch (selection) {
	case 'a': 
		char new_game_name[32];
		printf("새로운 게임 이름:");
		scanf("%s", new_game_name);
		create_new_game(new_game_name);
		break;
        //... 생략 ...//
	}
}

그리고 비주얼 스튜디오를 보면 빨간줄이 딱 떠있을 겁니다.

 

"선언에는 레이블을 사용할 수 없습니다." 

그러나 그냥 실행해도 문제없이 실행됨을 알 수 있는데, 이건 컴파일러에 따라서 그냥 실행할수 있게 해주는 경우입니다. 그렇지 않은 경우 아래와 같은 오류가 발생하며 컴파일이 안되는 경우가 있죠.

다음은 리눅스에서 위와 완전히 동일한 코드를 gcc를 이용하여 컴파일한 경우입니다. 같은 에러가 발생하면서 컴파일이 안되는 경우를 볼 수가 있죠?

 error: a label can only be part of a statement and a declaration is not a statement

 

해결방법은 매우 간단합니다. 중괄호로 case를 묶으면 에러가 발생하지 않습니다. 간단하죠?

 

이상으로 너무나 사용하기 쉬운 switch ~ case 문법에 대해서 알아보았습니다. 마지막에 대한 switch ~ case에 대한 실수는 많이 발생할 수 있으니, 알아두시면 인터넷 서치없이 바로 해결 가능합니다.

반응형
블로그 이미지

REAKWON

와나진짜

,

조건문 if

C언어를 배우는 중에 조건에 따라 실행흐름을 분기시키는 예약어인 if문에 대해서 자세히 알아보도록 합시다. if문와 else if는 괄호에 조건식을 써주는데요. 이 조건에 따라 실행을 다르게 시킬 목적으로 사용이 됩니다. 아래와 같은 방식으로 사용하게 됩니다. 이제 예제를 보면서 하나씩 이해하도록 합시다.

if(조건 1){
	//실행 1//
} else if(조건 2){
	//실행 2//
} else if(조건 3){
	// ... //
} else if(조건 n){
	//실행 n//
}else{
	//그 외 실행
}

 

1. if 단독 사용법

if문은 다음과 같이 괄호 안에 비교식을 써주면 되는데요. 이때 괄호식 안에 조건이 참이 되는 경우에만, if안의 코드를 실행하게 됩니다. if 안의 문장이 한문장일 경우에 아래의 중괄호는 생략이 될 수 있습니다.

#include <stdio.h>

void main() {
	int a = 10;
	if (a == 10) {	//if안의 구문이 한 줄이면 '{' 생략가능 
		printf("a = 10\n");
	}//if안의 구문이 한 줄이면 '}' 생략가능
	printf("종료\n");
}

위의 코드는 아래와 같이 수행이 됩니다. a가 5라면 if안에 printf는 호출이 되지 않지요.

 

2. if ~ else ~ 구문 예제

if에 조건에 맞지 않는 조건을 수행할 때에는 else 구문을 이용할 수 있습니다. 

#include <stdio.h>

void main() {
	int a = 2;
	if (a == 10) {	
		printf("a의 값은 10.\n");
	}
	else {
		printf("a의 값은 10이 아님.\n");
	}
}

아래는 위 코드의 결과입니다.

 

3. 조건이 여러가지일 경우 else if

조건이 여러 가지가 있을 경우에는 else if를 통해서 여러 조건을 줄 수가 있습니다. if 부터 순차적으로 아래쪽으로 비교해서 그 중 참이 되는 경우에 있는 블록만 실행하고 바로 if문을 빠져나옵니다.

#include <stdio.h>

void main() {
	int a = 3;
	if (a >= 5) {	
		printf("a의 값은 5 이상\n");
	}
	else if (a >= 2) {
		printf("a의 값은 2 이상\n");
	}
	else {
		printf("a의 값은 2 미만\n");
	}
}

#include <stdio.h>

void main() {
	int score = 85;
	if (score >= 90) {
		printf("학점 A\n");
	}
	else if (score >= 80) {
		printf("학점 B\n");
	}
	else if (score >= 70) {
		printf("학점 C\n");
	}
	else if (score >= 60) {
		printf("학점 D\n");
	}
	else {
		printf("학점 F\n");
	}
}

 

4. 조건이 참인 경우 = 0이 아닌 경우

C, C++에서 거짓의 조건은 간단합니다. 0만 false라고 간주한다는 것이죠. 그 외의 모든 수는 전부 참으로 간주합니다. 즉, 여기 if문이나 else if에서 조건이 참인 경우는 0이 아닌 경우입니다. 0외에 숫자는 모두 참으로 간주하게 됩니다. 그래서 아래의 코드중 a와 b가 참이되는 이유입니다.

#include <stdio.h>

void main() {
	int a = 1;
	int b = -1;
	int c = 0;
	if (a) {
		printf("a는 true\n");
	}
	if (b) {
		printf("b는 true\n");
	}
	if (c) {
		printf("c는 true\n");
	}
	printf("종료\n");
}

 

5. &&조건과 || 조건

&&(논리 AND 연산)은 모든 조건이 전부 true여야 true이고, ||(논리 OR 연산)은 모든 조건중에 하나라도 true이면 true입니다. 

#include <stdio.h>

void main() {
	int a = 1;
	int b = 2;
	if (a == 1 && b == 2) {
		printf("a=1이고 b=2이다.");
	}
	else {
		printf("a=1이 아니거나, b=2가 아니다.");
	}
}

6.1 하기 쉬운 실수1 (모든 비교문이 수행이 되지 않음)

다음은 if문이나 조건식에서 흔하게 하기 쉬운 실수입니다. 아래의 코드를 예측해보시기 바랍니다.

#include <stdio.h>

void main() {
	int a = 10;
	int b = 20;
	if (a > 10 && (b++) > 20) {
		printf("실행이 될까요?\n");
	}
	printf("b의 값:%d\n", b);
}

 

조건을 보게 되면 a는 10보다 크지 않죠. 딱 10이니까요. 그렇다면 a>10은 false가 됩니다. 

그리고 다음 조건에서 b를 선증가시킨 후 20과 비교합니다. b는 21의 값이 되겠네요. 그렇다면 b>20의 조건은 true가 되겠네요. 

그렇다면 if문 안의 printf는 실행이 되지 않겠네요. 그렇다면 b의 값은 얼마일까요? 아래 결과 화면입니다.

 

b의 값은 고스란히 20입니다. 조건식에서 비교할때 &&이나 ||이 오게 되면 true나 false가 확정일 경우에는 그 뒤의 식은 보지도 않고 건너 뛰어버립니다. 그래서 b++은 수행되지 않게 되죠. 그래서 b가 21로 나오게 할 경우에는 아래와 같이 수정이 되어야 21이 나오게 됩니다.

#include <stdio.h>

void main() {
	int a = 10;
	int b = 20;
	if (a > 9 && (b++) > 20) {
		printf("실행이 될까요?\n");
	}
	printf("b의 값:%d\n", b);
}

6.2 하기 쉬운 실수 2 (실수로 값 대입)

== 연산자나 != 연산자를 사용할때 흔히 이런 실수를 많이 하게 됩니다. 아래의 코드는 원래 finished == 1일 경우 프로그램이 종료되야하는 조건입니다. finished가 그전에 0이기 때문에 if문을 수행하지 않는것이 프로그래머의 의도입니다. 하지만 코드에서 프로그래머의 실수로 '=' 하나 빠지는 바람에 finished=0에서 finished=1로 대입이 되었고, if문이 실행이 됩니다.

#include <stdio.h>

void main() {
	int finished = 0;
	if (finished = 1) {	//이 식은 문법상 문제가 없는 문장이므로 에러없이 컴파일됨
		printf("finished\n");
	}
}

 

이런 오류들은 컴파일에서 잡아낼 수 없으며 찾는데도 오래 걸리게 됩니다. 따라서 비교식을 쓸때 상수는 왼쪽에 쓰는 것이 이런 휴먼 에러를 막을 수 있습니다.

이렇게 빨간색으로 뜨니까 컴파일단계에서 바로 알 수 있고 아래처럼 올바르게 수정하여 프로그램을 더 안전하게 만들 수 있습니다.

#include <stdio.h>

void main() {
	int finished = 0;
	if (1 == finished) {
		printf("finished\n");
	}
}

 

여기까지 C언어의 if문에 대해서 알아보았습니다. if문은 어렵지는 않지만 비교문이 끝까지 비교가 진행이 되는지 되지 않는지 모르는 것과 아는 것은 다릅니다. 공부하는데 도움이 되었으면 좋겠습니다.

반응형
블로그 이미지

REAKWON

와나진짜

,

ASCII to Hex

간단히 말해서 입력이 String인데, 이것을 16진수 값으로 변환하는 코드를 구현해보도록 하겠습니다.

간단히 "CAFECAFE0102"라는 이런 문자열을 입력을 받았는데 16진수로 변환하고 싶은 것입니다.  즉, 0xCA, 0xFE, 0xCA, 0xFE, 0x01, 0x02의 숫자 배열로 입력을 변환하는 것이죠.

가장 간단하게 구현하기 위해서 매우 정상적인 입력값만 들어온다고 가정하겠습니다. 그러니까 16진수의 범위에 있지 않은 글자('G'를 넘어가는 알파벳)는 들어오지 않는다고 가정하겠습니다.

설명은 아래의 주석으로 대체하도록 하겠습니다.

 

 

#include <stdio.h>
#include <stdint.h>
#include <string.h>


//입력의 str은 모두 대문자인 16진수 변환가능한 문자열이라고 가정
//size는 str의 크기
//hex는 변환된 16진수 배열
unsigned int ascii_to_hex(const char* str, size_t size, uint8_t* hex)
{
    unsigned int i, h, high, low;
    for (h = 0, i = 0; i < size; i += 2, ++h) {
        //9보다 큰 경우 : 알파벳 문자 'A' 이상인 문자로, 'A'를 빼고 10을 더함.
        //9이하인 경우 : 숫자 입력으로 '0'을 빼면 실제 값이 구해짐.
        high = (str[i] > '9') ? str[i] - 'A' + 10 : str[i] - '0';
        low = (str[i + 1] > '9') ? str[i + 1] - 'A' + 10 : str[i + 1] - '0';
        //high 4비트, low 4비트이므로, 1바이트를 만들어주기 위해 high를 왼쪽으로 4비트 shift
        //이후 OR(|)연산으로 합
        hex[h] = (high << 4) | low;
    }
    return h;
}

int main() {
    char str[128] = "CAFEcafe0102";
    uint8_t hex[128] = { 0, };
    size_t size = strlen(str);
    int i;

    //소문자 cafe를 대문자로 만들기 위해 strupr함수 사용
    strupr(str);
    //hex에 실제 16진수의 값이 들어감
    ascii_to_hex(str, size, hex);
    
    //size는 반으로 줄어듦, ex) hex[0]=0xCA;
    for (i = 0; i < (size/2); i++)
        printf("0x%02X\n", hex[i]);
}

 

1바이트의 unsigned int 자료형인 uint8_t를 사용하기 위해서는 stdint.h를 include해야합니다.

결과

 

 

사실 ascii에 대한 개념과 char 자료형이 정수처럼 계산할 수 있다는 사실, 그리고 비트 연산(SHIFT, OR) 연산만 이해하고 알고 있다면 그렇게 어려운 구현은 아니라고 생각이 됩니다.

반응형
블로그 이미지

REAKWON

와나진짜

,

 

수행 시간 구하기(clock 함수)

 

여러분이 짠 프로그램의 얼마나 효율적으로 동작하는지 궁금하다거나 회사에서 솔루션을 개발하다가 본인의 함수가 얼마나 빠르게 동작하는 지 확인해볼 필요가 있습니다. 그럴때 프로그램이 얼마나 실행됐는지 궁금하시다면 clock()함수로 구할 수가 있습니다. clock함수는 아래와 같이 정의가 되어있습니다. time.h 헤더파일을 include해야함을 알 수가 있네요.

 

#include <time.h>
clock_t clock(void);

 

이 함수에 대해서 프로그램 수행시간은 아래에서 실제 구현하긴 할 것인데, 우선 이 함수에 대해서 설명부터 하도록 하겠습니다.

clock함수는 clock_t라는 값을 반환하는데 이 값은 CPU가 사용된 값을 나타냅니다. 정확히는 시간이 아닌 clock수를 반환하지요. 초 단위로 구하려면 초당 클록수를 알아야하는데 아래의 매크로를 사용하면 됩니다.

CLOCKS_PER_SEC

이처럼 초 단위로 바꾸고 싶다면 CLOCKS_PER_SEC라는 매크로로 나누어서 초단위로 구할 수 있습니다. clock수를 어떤 이유로 얻어올 수 없다면 (clock_t) -1 이 반환됩니다.

또한 이 함수는 Thread-Safety한 특징을 갖습니다.

이제 정말 프로그램의 수행시간을 알아내는 코드를 보도록 합시다. 아래의 코드는 for루프를 수행한 수행시간을 구합니다. 1초 이내의 끝이 날 것이기 때문에 보다 밀리-세컨 단위의 더 정확한 시간을 측정하기 위해서 clock_t 자료형에서 double로 형변환을 한 것입니다.

 

#include <time.h>
#include <stdio.h>

int main(void)
{
    int  i;
    double start, end;

    //for 루프 시작 시간
    start = (double)clock() / CLOCKS_PER_SEC;    

    //for루프 100000000번 돌아보기
    int sum = 0;
    for (i = 0; i < 100000000; i++) {
        sum++;
    }

    //for 루프 끝난 시간
    end = (((double)clock()) / CLOCKS_PER_SEC);
    printf("프로그램 수행 시간 :%lf\n", (end-start));
}

 

결과 

 

수행 결과

 

제 컴퓨터가 좋은 편이 아니라 0.436초 걸리고 난 후에 프로그램이 종료되었음을 알 수 있네요. 프로그램 수행시간을 측정하는 방법은 이처럼 clock() 함수를 이용해서 구해낼 수 있습니다. 이렇게 우리가 만든 알고리즘이 얼마나 걸리는지도 확인해볼 수 있겠습니다.

 

반응형
블로그 이미지

REAKWON

와나진짜

,

문자열을 숫자로 변환하는 함수

문자열을 숫자 자료형으로 변환하려면 어떤 방법으로 변환할 수 있을까요? 만약 입력이 숫자의 문자열이라고 가정한다면 아래의 코딩으로 숫자로 변환할 수 있습니다.

#include <stdio.h>

int my_atoi(const char* str) {
	int ret = 0;
	int i;
	for (i = 0; str[i] != '\0'; i++)	//NULL문자만나면 for문 종료
		ret = ret * 10 + (str[i] - '0');	//숫자 문자에 '0'을 빼면 레알 숫자가 구해짐
	return ret;
}
int main() {
	int str = "12345";
	printf("%d\n", my_atoi(str));

}

 

문자 하나에서 '0'을 빼면 이것이 곧 숫자 0이 된다는 점을 이용한 코드이지요. 사실 이렇게 간단하게 짤 수도 있겠지만 문자열에 숫자말고도 알파벳같은 글자가 섞여 들어오면 처리가 필요하게 됩니다. 코드짤때 에러 처리하는 것은 여간 귀찮은 것이 아니지요. 위 처럼 접근하는 것은 좋긴하지만 급할때 삽질하지 말라고 아래의 함수들을 C언어 라이브러리에서 제공해줍니다.

 

지금 소개해드릴 함수들이 그런 함수들입니다. 아래의 함수들을 사용하기 위해서는 stdlib.h 헤더파일을 우선 include시켜줘야한다는 점 기억하시구요.

 

atoi(Ascii To Integer) - int로 변환

#include <stdlib.h>
int atoi(const char* str);

atoi함수는 문자열을 정수형으로 변환시켜주는 함수입니다. 함수명의 앞글자 a는 Ascii를 의미합니다. 이후의 모든 함수의 a는 ascii의 a라는 점은 알아두시면 나중에 배운척 할 수 있습니다. 이 함수가 받는 str 문자열에서 숫자가 아닌 글자들은 모두 무시됩니다.

변환할 수 없는 값이라면 0을 반환합니다.

 

atol(Ascii To Long) - long으로 변환

atoll(Ascii To Long Long) - long long int로 변환

#include <stdlib.h>
long int atol(const char *str);
long long int atoll(const char *str);

long 형태로 입력을 받고 싶다면 atol 함수를 이용하면 됩니다. 이것도 모자라 long int의 범위를 넘어서는 큰 수를 입력받고 싶을때가 있습니다. 보다 큰 범위의 수를 변환하기를 원한다면 atoll 함수를 이용하여 변환할 수 있습니다. 

반환할 수 없으면 0L을 반환합니다.

 

atof(Ascii To Float) - 부동소수점 값으로 변환

#include <stdlib.h>
double atof(const char *str);

정수형말고도 소숫점으로 변환할 수 있는 함수도 있습니다. atof라는 함수이지요. 전달받은 문자열을 부동 소수점 값으로 변환해주는 함수입니다. 여기서 float자료형을 반환하는 게 아닌 double 자료형으로 return 한다는 것을 유의하세요. 

만약 함수를 통해서 반환할 수 없다면 반환값은 0이 됩니다.

 

단순한 설명보다는 무엇보다 어떻게 사용하는지 코드를 보면서 이해를 하는게 좋습니다. 

아래의 코드는 기본적인 함수들의 사용법을 보여주는 예제입니다.

#include <stdio.h>
#include <stdlib.h>
int main() {
	char* ll_str = "987654321123456789";
	char* i_str = "1234";
	char* f_str = "1234.567";
	//long long 자료형 출력
	printf("%lld\n", atoll(ll_str));

	//정수형 10진수로 출력
	printf("%d\n", atoi(i_str));

	//부동 소수점으로 출력
	printf("%.3f\n", atof(f_str));

	i_str = "1234에 문자열 꼽사리";
	printf("%d\n", atoi(i_str));

}

 

결과 화면

 

위 결과에서도 확인할 수 있듯이 "1234"에 다른 문자열 "에 문자열 꼽사리"를 섞어도 숫자만 추출해내는 것을 알 수 있습니다. 

이처럼 C언어에서는 문자열을 숫자로 변환시켜주는 함수를 제공하니 알맞게, 필요할때 사용하시면 되겠습니다.

반응형
블로그 이미지

REAKWON

와나진짜

,

C언어의 다양한 출력 문자들

C언어에서 다양한 출력 형식을 지원합니다. 우리가 너무나 잘 알고 있는 부호있는 정수형은 %d, 문자열 출력은 %s 등이 그 출력형식인데요. 오늘은 자세하게 한번 총 정리하는 포스팅을 정리하도록 하겠습니다. 마지막에는 단순 화면에 출력하는 것이 아닌 변수에 저장하는 방법, 파일에 출력하는 방법을 알아보도록 하겠습니다. 더불어 이 형식들과 소개한 함수를 이용해서 ls가 출력하는 형식처럼 일정한 간격으로 보기 좋게 문자열을 출력하는 방법도 소개합니다.

 

출력 문자(Format Character)

출력 형식 설명 출력 예
%d 우리가 흔히 알고 있는 부호 있는 정수형을 출력해줍니다. printf("%d",-150); -150
%c 문자열 하나를 출력해줍니다. printf("%c",'A');  A
%p 주로 주소를 출력합니다. 메모리 크기만큼 자릿수가 채워집니다. 출력될때는 16진수로 표시됩니다. int a;
printf("%p",&a);
0177F95C
%x 정수를 16진수로 출력합니다. %x를 보시면 소문자인데, 16진수로 표시될때 알파벳은 소문자로 표시됩니다. printf("%x",10); a
%X 위의 %x와 동일하나 알파벳이 대문자로 표시됩니다. printf("%X",10); A
%o 8진수로 출력합니다. printf("%o", 8888); 21270
%s 문자열을 출력합니다. '\0'인 NULL문자를 만날때까지 출력이 됩니다. printf("%s","hello, world"); hello, world
%u 부호없는 정수 출력입니다. 즉, 양수를 출력하는 포맷입니다. signed bit을 정수 데이터로 취급한다는 것이죠. 만약 -1이라는 정수가 2의 보수를 거치는 과정은 이렇습니다.  0000 0000 0000 0000 0000 0000 0000 0001 -> 1111 1111 1111 1111 1111 1111 1111 1110 + 1-> 1111 1111 1111 1111 1111 1111 1111 1111 = 4294967295 이 되죠. 부호가 있다면 -1이지만 부호없으면 4294967295라고 이렇게 표시가 된다는 뜻이에요. printf("%u",1);
printf("%u",-1);
1
4294967295
%ld 부호있는 long 형 정수 출력입니다. printf("%ld", 1L); 1
%lld 부호있는 long long 형 정수 출력입니다. printf("%lld", 5294967295); 5294967295
%lu 부호없는 long 형 정수 출력입니다. printf("%lu", -12345); 4294954951
%llu 부호없는 long long 형 정수 출력입니다. printf("%llu",-12345); 18876570244468679
%f, %lf 두 개 모두 실수형을 출력합니다. 6자리까지 출력이 되면 그 이하는 반올림처리됩니다. 모자라면 0으로 채웁니다.
출력할때는 상관없지만 입력 받을때는 float 자료형에 넣을시 %f, double에 입력받을때는 %lf를 사용합니다.

float f;
double d;
scanf("%f",&f);
scanf("%lf",&d);
--------------------------
printf("%f",0.1234f);
printf("%f",0.1234567f);
0.123400
0.1234567
%e 실수를 지수 표기법으로 소문자로 표시합니다.  printf("%e", 0.11223344f); 1.122334e-01
%E 실수를 지수 표기법으로 표시할때 대문자를 사용합니다. printf("%E", 0.11223344f); 1.122334E-01

 

형식 정렬 (Format Alignment)

'%' 문자와 포맷 문자(d, x, u 등) 사이에 부호와 숫자를 넣을 수 있는데, 숫자는 공간을 의미합니다. 만약 공간이 남으면 빈 자리 만큼 공백이 생깁니다. 이때 하고 부호('-')가 있으면 왼쪽 정렬, 아니면 오른쪽 정렬입니다. 예를 보면서 확인하세요.

출력 형식 출력 설명
printf("%-4d, %d, %4d", 10,11,12); 10  , 11,   12 4자리 공간을 확보하며 10은 왼쪽 정렬, 12는 오른쪽 정렬을 하여 출력하니다.
printf("%-4d, %d, %4d", 10000,11000,12000); 10000, 11000, 12000 4자리를 넘어갈 경우 일반 출력과 같습니다.
printf("%8x",0xFFEE)     ffee 8자리 확보하고 오른쪽 정렬합니다.
printf("%08x",0xFFEE) 0000ffee 8자리를 확보하고 빈 공간은 0으로 채웁니다.
printf("%02x %02x %02x", 0xE, 0xF1, 0xDEAD);  0e f1 dead 2자리를 확보합니다. 빈자리는 0으로 채우고 넘는다면 일반 출력과 같습니다.
printf("%.3f", 3.14f);
printf("%.3f", 3.141592f);
3,14
3.142
소수점 이하 3자리까지 출력합니다. 자리수가 넘어가지 않을 경우 0으로 채우며 넘어가면 3자리에서 반올림하여 출력합니다.
printf("%10.3f", 3.14f);      3.140 10자리 확보한 이후 수를 오른쪽 정렬로 출력합니다. -는 왼쪽 정렬이겠죠?
printf("%10s", "reakwon");    reakwon 10자리 확보 후 오른쪽 정렬로 문자열을 출력합니다.

 

n진수 문자 포함 출력

8진수나 16진수일때 n진수 문자를 앞에 표기할때 문자 '#'을 사용합니다. 여기서는 16진수만 예를 들어보겠습니다.

출력 형식 출력 설명
printf("%#x", 0xFF11); 0xff11 16진수 표기법인 0x까지 출력합니다.

 

%기호를 자체 출력

%는 정말 문자 그대로 출력하려면 %% 두번쓰면 됩니다. 

출력 형식 출력 설명
printf("%%JAVA_HOME%%bin"); %JAVA_HOME%bin %를 문자로 출력했습니다. %%를 사용하였죠.

 

우리는 단순히 화면에 출력했었죠? 하지만 가끔 프로그래밍을 하다가 보면 화면에 출력하는 방식이 아닌 변수에 담거나, 파일에 직접써야하는 일이 생깁니다. 그럴때 유용한 함수들이 존재하는데 여기서 알아볼 함수는 sprintf fprintf입니다. 

sprintf - 문자 배열에 형식 문자열 write

만약 콘솔에 출력하는 대신 일반 문자열에 저장하려면 어떻게 할까요? 이런 기능을 맡는 함수가 sprintf 함수입니다. sprintf의 첫 인수는 저장할 문자 버퍼(문자 배열), 두번째는 포맷 문자열, 세번째는 형식에 대응되는 데이터들입니다.

	char str[128];
	sprintf(str, "%d + %d = %d", 10, 20, 10 + 20);
	printf("%s\n", str);
10 + 20 = 30

 

sprintf 예제 ) 문자열을 일정한 간격으로 출력

리눅스 ls명령을 보면 신기하게도 문자열을 고른 간격으로 출력하는 것을 볼 수 있죠. 신기하지 않았나요? 나만 그런가.. 여기서 sprintf를 사용하여 흉내낼 수 있습니다. 

 

어떻게 흉내낼까요? 아래의 코드가 흉내내는 코드입니다. 설명은 주석으로 충분할 것 같습니다.

#include <stdio.h>
#include <string.h>
int main() {
	int i;
	char name[][100] = { "reakwon","kim","john","lee","yu","loooooooooooooooong","hello!","good!" };
	char format[128];	//출력 형식을 담을 문자열
	int longest = 0;	//가장 긴 문자열의 길이
	
	for (i = 0; i < 8; i++) {
		int len = strlen(name[i]);
		if (len > longest)		
			longest = len;
	}

	// '%' + '-' + %u = longest+3] + s -> %-[longest+3]s : 왼쪽 정렬된 문자 공간 longest+3 자리 확보
	sprintf(format, "%%-%us", longest+3);	
	for (i = 0; i < 8; i++) {
		if (i != 0 && i % 2 == 0) printf("\n");
		printf(format, name[i]);
	}

}

 

일정한 길이의 문자열 출력

 

fprintf - 파일에 형식 문자열 출력

콘솔에 출력하는게 아니라 파일에 출력하려면 fprintf함수를 사용하면 됩니다. sprintf와 사용법은 거의 비슷한데 첫 인자를 파일로 지정하면 됩니다. 어려운것은 없죠?

	FILE* fp = fopen("file.txt", "w");

	fprintf(fp, "%s, %s", "reakwon", "tistory");
	fclose(fp);

 

fprintf 사용법

 

여기까지 C언어에서 지원하는 출력 형식(format)을 알아보았고 콘솔에 출력하지 않고 문자 배열에 저장하는 함수인 sprintf와 파일에 출력해주는 유용한 함수 fprintf에 대해서 알아보았습니다. 유용한 함수이고 어려운 것은 없으니 기억해두셔서 적재적소에 쓰시면 되겠습니다.

반응형
블로그 이미지

REAKWON

와나진짜

,

 

 

C언어, C++에서는 메모리를 조금 더 쉽게 다루고자하는 함수가 몇가지 존재합니다. 그것들이 무엇이 있는지 설명과 예제를 통해서 알아보도록 하겠습니다. 메모리 관련 함수를 사용하기 위해서는 string.h를 include해야합니다.

 

0) string.h 헤더파일 추가

메모리 관련 함수를 사용하기 위해서 반드시 추가해주세요.

 

1) void* memset(void* source, int value, size_t n)

메모리 주소 source부터 시작해 n만큼 value로 메모리를 채웁니다. return 값은 메모리의 시작주소입니다.

간단하네요. 그러면 예제를 바로 보도록 하겠습니다.

#include <stdio.h>
#include <string.h>

int main() {

	int nums1[5];
	unsigned char nums2[5];
	int i;

	memset(nums1, 10, sizeof(nums1));
	memset(nums2, 10, sizeof(nums2));
	
	for (i = 0; i < 5; i++) {
		printf("nums1[%d] = %d \n", i, nums1[i]);
	}
	
	printf("\n");

	for (i = 0; i < 5; i++) {
		printf("nums2[%d] = %d \n", i, nums2[i]);
	}
}

 

nums1과 nums2를 5개의 배열로 잡는데 자료형이 다르군요. nums1는 int형(여기서는 4바이트), nums2는 unsigned char형(1바이트)입니다. 이후 둘의 메모리를 memset으로 10으로 초기화합니다.

어떤 결과가 나올까요? 두개의 for문에서 nums1과 nums2의 요소들이 전부 10으로 나올것 같은데 그럴까요?

실행결과

nums1[0] = 168430090
nums1[1] = 168430090
nums1[2] = 168430090
nums1[3] = 168430090
nums1[4] = 168430090

nums2[0] = 10
nums2[1] = 10
nums2[2] = 10
nums2[3] = 10
nums2[4] = 10

 

우리의 예상과는 조금은 다릅니다. memset내부에서 실제 10이란 값은 unsigned char로 변환되어 1바이트의 메모리에 그 값을 집어넣게 되는겁니다. 

그래서 4바이트인 int형은 이런식으로 메모리가 set이 됩니다.

00001010 00001010 00001010 00001010 -> 168430090

memset은 1바이트 단위의 메모리를 세팅합니다. 그래서 unsigned char 형의 nums2는 제대로 된 값을 읽을 수 있습니다.

 

2) void* memcpy(void* destination, const void* source, size_t num)

이 함수는 source의 메모리를 destination으로 num만큼 복사합니다. 이 함수에는 source나 destination이 num바이트 이상인지를 검사하지 않으므로 상당히 취약하며 이진데이터를 그대로 복사합니다. 그러니 중간에 NULL이 있는지 없는지 확인하지 않습니다. 아래의 예제를 봅시다.

#include <stdio.h>
#include <string.h>

int main() {

	unsigned char source[8];
	int destination[2];
	int i;

	memset(source, 10, sizeof(source));

	memcpy(destination, source, sizeof(source));

	for (i = 0; i < 2; i++) {
		printf("destination[%d] : %d\n", i, destination[i]);
	}
}

 

source는 8바이트이고 destination도 8바이트입니다. 우선 source를 10으로 전부 채운 후에 destination으로 메모리 복사를 하면 어떤 결과가 나올까요?

 

이전의 memset에서 보았듯 바이트 단위로 메모리가 복사되어 8바이트가 0000 1010으로 복사되는 것이지요.

00001010 00001010 00001010 00001010 -> 168430090

따라서 168430090의 값이 두 번 출력되게 됩니다.

실행 결과

destination[0] : 168430090
destination[1] : 168430090

 

 

3) int memcmp(const void* ptr1, const void* ptr2, size_t num)

메모리의 바이트를 비교합니다. ptr1과 ptr2가 num만큼 비교했을 때 같다면 0, 아니면 다른 값을 리턴합니다. strcmp와 비슷한 리턴 값을 보이는데, unsigned char으로 ptr1이 ptr2보다 크다면 양수, 작다면 음수를 리턴하게 됩니다.

 

#include <stdio.h>
#include <string.h>

int main() {

	unsigned char a[5] = { 0,1,2,3,4 };
	unsigned char b[5] = { 0,1,2,3,4 };

	printf("memcmp(a,b) = %d \n", memcmp(a, b,5));
	
	a[0] = 100;
	
	printf("memcmp(a,b) = %d \n", memcmp(a, b, 5));

	b[0] = 200;

	printf("memcmp(a,b) = %d \n", memcmp(a, b, 5));
}

 

처음 a,b는 정확히 같은 값을 갖고 있으므로 비교했을때 0이 리턴됩니다.

이후 a의 0번째 요소가 100으로 a가 b보다 더 크므로 비교했을때 양수가 리턴됩니다. 

그 다음 b의 0번째 요소가 200으로 a가 b보다 더 작으므로 음수가 리턴되지요.

실행 결과

memcmp(a,b) = 0
memcmp(a,b) = 1
memcmp(a,b) = -1

 

4) void* memchr(void* ptr, int value, size_t num)

memchr은 ptr에서 value를 찾을때 사용합니다. 즉 메모리에서 특정 값을 찾을 때 사용하는 함수입니다. 만약 값이 존재한다면 그 주소를 리턴하고 아니면 NULL을 반환합니다.

 

#include <stdio.h>
#include <string.h>

void printMemory(void *ptr) {
	if (ptr == NULL) {
		printf("메모리에 존재하지 않음\n");
	}
	else {
		printf("메모리에 %d가 존재. addr : %p \n", *((unsigned char*)ptr),ptr);
	}
}
int main() {

	unsigned char arr[5] = { 0,1,2,3,4 };
	unsigned char a = 4;
	unsigned char b = 5;
	int i;

	for (i = 0; i < 5; i++) 
		printf("arr[%d] : %d, %p\n", i, arr[i], &arr[i]);
	
	
	void* ptr=memchr(arr, a, 5);
	printMemory(ptr);

	ptr = memchr(arr, b, 5);
	printMemory(ptr);
}

 

현재 1바이트 배열 arr에는 0,1,2,3,4가 있습니다. 여기에서 a(4)와 b(5)를 찾을 겁니다. a는 존재하니까 ptr이 NULL이 아닌 a가 존재하는 그 주소를 반환하겠지요. b는 존재하지 않으므로 NULL이 반환됩니다.

 

실행결과

arr[0] : 0, 0093F75C
arr[1] : 1, 0093F75D
arr[2] : 2, 0093F75E
arr[3] : 3, 0093F75F
arr[4] : 4, 0093F760
메모리에 4가 존재. addr : 0093F760
메모리에 존재하지 않음

 

4가 있는 주소, 0093F760을 반환하는 것을 알 수 있네요.

 

 

반응형
블로그 이미지

REAKWON

와나진짜

,