C 언어 다시 돌아보기

GGG·2022년 5월 27일
0
post-thumbnail

모두의 코드 C

절대로 바뀌지 않을 것 같은 값에는 무조건 const 키워드를 붙여주는 습관을 가지자.

배열을 가리키는 포인터

int arr[2][3] = {{1,2,3}, {4,5,6}};
int (*parr)[3] = &arr;
// 첫 차원을 제외한 부분의 크기에 대한 정보가 있어야함.
int *parr2[3]
// 위와 같이 적으면 포인터 3개를 가지는 배열.

printf("arr[1][2]: %d, parr[1][2]: %d", arr[1][2], parr[1][2]_;

함수
특정한 변수의 값을 다른 함수를 통해 바꾸려면 변수의 주소값을 전달해야 한다!

int multi(int (*arr)[3][2][5]){
	// n, 3, 2, 5
	arr[1][1][1][1] = 1;
    return 0;
}

int multi2(int arr[][3][2][5], int i){
	arr[1][1][1][1] = i;
    return 0;
}
// 함수에서는 배열을 인자로서 이런식으로 적을 수 있음.
// 함수 아니면 마지막 부분 제외하고는 다 채워져야함.

int (*pfunc)(int (*)[3][2][5], int) = multi2;
// 함수 포인터: (함수의 리턴형) (*포인터이름)(첫번째 인자 타입, 두번째 인자 타입...)

int arr[3][3][2][5] = {0};
pfunc(arr, 3)

문자열
문자열의 끝에는 종료 문자 Null이 들어감. -> 3글자를 입력하더라도 배열은 4칸 필요.

"" : 문자열을 지정하는데 사용. ex) "abc", "defgh"
'': 한 개의 문자를 지정할 때 사용. ex) 'a', 'b', 'c'

버퍼
scanf 등을 이용할 때에는 stdin 버퍼 사용. ' ', '\n', '\t'를 만나기 전까지 값을 가져간 후 버퍼에서 삭제. 이러한 과정에서 버퍼에 값이 남아있는 현상이 발생할 수 있음.

  • %c를 이용하는 경우 버퍼에 무엇이 남아있는지를 잘 고려해야함.
  • 하나를 제거하기 위해서는 getchar() 함수 사용 가능.
  • 결론: 문자 대신 문자열을 받는 것이 유리하다. %c 보다는 %s 사용하자.

리터럴
Literal: 소스 코드 상에서 고정된 값을 가지는 것.

  • C언어에서 큰 따옴표로 묶은 것들을 문자열 리터럴(string literal)이라고 부름.

컴퓨터는 이러한 리터럴들을 모아 따로 보관. -> 오직 읽기만 가능한 곳.

char *ptsr = "string literal"
// string literal의 시작 주소값을 가져와서 ptsr에 대입하는 작업.
// 값을 바꾸려고 하면 오류 발생.

char str[] = "hello"
// 여기서는 리터럴이라고 부르기 애매함. 컴파일러에서 아래와 같이 해석.

char str[] = {'h','e','l','l','o'}
// 배열에 hello라는 문자열을 복사.
// 텍스트 세그먼트가 아닌 스택이라는 메모리 수정이 가능한 영역에 정의. 수정 가능.

문자열 사용시 주의점
문자열은 더하는 연산 X, ==로 비교 연산 X, =으로 대입 연산 X

// copy를 위한 함수
int copy_str(char *dest, char *src)
{
	while (*src){
    	*dest++ = *src++;
    }
    *dest = '\0'; // NULL 문자
    return 0;
}
char str[100];
str = "abcdef" // 오류 발생. 리터럴의 주소값을 넣어주라는 연산이 되는데, 배열의 주소값은 바꿀 수 없음.

char str[100] = "abcdef" // C언어에서 편의상 제공하는 방법으로 이건 오류 없음.

변수의 종류
local variable: 지역 { } 내 정의된 변수. 지역을 빠져나갈 때 파괴.
global varible: 어떠한 지역에도 속해있지 않은 변수. 코드 어느곳에서도 접근할 수 있음. 0으로 자동 초기화.
많이 사용하지 않는 것을 권장
static variable: 선언된 범위를 벗어나더라도 절대로 파괴되지 않음. 0으로 자동 초기화.

왜 0으로 초기화될까?
C standard에서 규정. 효율성 측면. 주소가 알려져 있고 고정되므로, 0으로 초기화하는 것은 runtime cost가 없음.
지역 변수는 런타임 중에 정의되므로 초기화를 하게 되면 cost 발생 가능. 필요한 경우에만 초기화.

데이터 세그먼트의 구조

모두의 코드 자료 그래프 참고
메모리 주소가 작아지는 방향으로 스택, 힙, 데이터 영역, Read-Only Data(상수와 리터럴), 코드 영역으로 나누어짐. 스택에는 지역변수가 위치. 메모리 주소가 낮아지는 방향으로 확장.

헤더파일과 include
Compile: 단일 소스 코드 전체를 어셈블리어로 변환.
Linking: 각기 다른 파일에 위치한 소스 코드들을 한데 엮어서 하나의 실행 파일로 만드는 과정.

파일 나누더라도 위에서 함수의 형태를 명시해주어야 함수의 형태를 파악해 오류가 발생하지 않음.

헤더파일

  • 함수의 원형, 전역 변수, 구조체, 공용체, 열거형, 일부 특정한 함수(인라인 함수), 매크로.
    <>는 컴파일러에서 기본으로 지원하는 헤더파일.
    ""는 사용자가 직접 제작한 헤더파일.

modular programming: 파일을 나누어서 처리.
main 파일에서 하는 일을 적게 만들어보자!

#include <string.h>

strcmp(str1, str2) // 문자열 비교. 같으면 0 다르면 다른 값.
strcpy(dest, src) // 문자열 복사

구조체
배열은 같은 타입의 원소만 보관. 다른 타입의 데이터 보관하는 것? -> 구조체
구조체에서는 원소보다는 member라고 부름.
구조체 포인터에서는 -> 통해서 인자 접근 가능. * 사용시 우선 순위 .보다 낮으므로 주의!

struct Human
{
    char name[30];
    int age;
    int height;
    int weight;
};

int main()
{
    struct Human GJS;
    GJS.age = 30;
    GJS.height = 175;
    GJS.weight = 72;
    copy_str(GJS.name, "Jin Soo");

    struct Human *human_ptr = &GJS;

    printf("human name: %s\n", human_ptr->name);
    printf("human height: %d\n", GJS.height);
    printf("human weight: %d\n", (*human_ptr).weight);
}

전처리기
#이 들어간 명령들. 끝에 ;를 붙이지 않음. 컴파일 이전에 정확하게 대체 됨.

#include

#define 매크로이름 값. //매크로이름을 모두 값으로 바꾸어줌.

#ifdef 매크로이름 // 매크로이름이 정의되어있다면 ~ #endif 까지가 코드에 포함됨.
#endif

#ifndef		// 위와 정반대.
#else		// if 문이 아닐 때.
#endif

void

// void a는 주소값 얼마나 할당되어야하는지 알 수 없음. 오류
void* a; // 동작 O. 주소값의 보관 역할만 수행.
double b = 12.34;
a = &b;

printf("%lf", *(double *)a);
// 이와 같은 형태로 void 포인터 사용 가능.
// 어떤 타입의 포인터 주소값도 편리하게 담을 수 있기 때문에 많은 부분에서 활용되고 있음.

main 함수의 인자

#include <stdio.h>
int main(int argc, char **argv) {
	printf("받은 인자 개수 : %d \n", argc);
	printf("인자 : %s \n", argv[0]);
	// 추가 인자들도 argv[1], argv[2] ...로 저장됨.

  return 0;
}

char **argvchar* argv[3]를 가리키는 포인터형으로 이해. argv는 포인터들의 배열을 가리키고 있음.

메모리 동적 할당

#include <stdlib.h>

int main(int argv, char** argv)
{
    int size_arr;
    int* arr_malloc;
    printf("만들고 싶은 배열의 원소 수: ");
    scanf("%d", &size_arr);

    arr_malloc = (int *)malloc(sizeof(int)*size_arr);
    // malloc은 할당해준 메모리의 시작주소를 리턴.
    // (void *)로 리턴하기 때문에 적절한 형변환 필요.
    // 힙 영역.
    free(arr_malloc);
    // 할당받은 메모리 영역을 돌려주어야함!

    // 2차원 배열 동적 할당
    int arr_rows, arr_cols;
    int** arr_2d;

    printf("만들고 싶은 2d 배열의 원소 수: ");
    scanf("%d %d", &arr_rows, &arr_cols);
    arr_2d = (int **)malloc(arr_rows * sizeof(int));
    for (int i=0; i<arr_rows;++i)
    {
        arr_2d[i] = (int *)malloc(arr_cols * sizeof(int));
    }

    for (int i=0; i<arr_rows;++i)
    {
        free(arr_2d[i]);
    }
    free(arr_2d);
}

2차원 배열처럼 생긴 포인터 배열은 2차원 배열과는 달리 함수의 인자로 int **array와 같이 넘겨줄 수 있음. main함수의 argv와 유사한 성격.

malloc은 느린 함수. 최소한으로 하는 것이 좋음.
메모리가 연속적으로 있는 경우 접근이 빠름. gcc로 컴파일하거나 비주얼 스튜디오에서 확장자를 c로 지정해 사용가능.
int (*arr)[width] = (int (*)[width])malloc(height * width * sizeof(int));

매크로 함수

#define square(x) x * x // 컴파일 되기 전 전처리기에 의해 변환됨.
/*
square(3) 3*3
square(3+1) = 3 + 1 * 3 + 1

이런 상황 방지 위해 #define square(x) (x)*(x)가 적합.
*/

#define PrintVarable(var) printf(#var "\n")
#define AddName(x, y) x##y
/*
전처리기 내 #은 인자를 문자열로 바꿈. C에서 연속한 두개의 문자열은 그냥 합쳐짐.
전처리기 내 ##은 하나로 합쳐주는 역할
*/

괄호를 제대로 사용하지 않으면 오류가 날 수 있음!

인라인 함수
함수를 호출하게 되면 main함수를 벗어나 메모리 어딘가에 위치한 함수로 찾아가 값을 전달하고 리턴값을 받아옴. 함수의 호출과정은 시간이 드는데, 매크로함수와 유사하게 inline 함수를 사용하게되면 컴파일러가 보통 함수를 사용하는 것처럼 바꾸어줌.

__inline int square(int a) { return a * a; }

typedef
typedef (이름을 새로 부여하고자 하는 타입) (새로 준 타입의 이름)
struct 등을 이용해 구성한 타입을 간단하게 호출하고자 할 때 사용 가능. 기존의 이름도 사용 가능.

typedef struct Human {
	char[20] name;
    int age;
    int height;
    int weigth;
} Human

volatile
volatile type name 값이 변할 수 있기 때문에 최적화 작업을 수행하지 않게함.

pragma
현재 컴퓨터에서는 4바이트 단위로 모든 것을 처리하는 것이 빠름.

#pragma onece 헤더 파일 위에 넣으면 중복 포함 막을 수 있음.

파일 입출력

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

if (fp==NULL){
	printf("error\n");
    return 1;
}
fput("hi\n", fp);
fclose(fp);

char buf[20]; // 내용 입력
fp = fopen("a.txt", "r");
if (fp==NULL){
	printf("error\n");
    return 1;
}
fgets(buf, 20, fp); /// 오버플로우 방지. 20바이트 입력받을 것이라 명시.
fclose(fp);
printf("%s\n", buf);
return 0;

FILE *fp = fopen("a.txt", "r");
char c;

while ((c = fgetc(fp)) != EOF) {
	//EOF: -1
	printf("%c", c);
}

fclose(fp);

fseek(fp, 0, SEEK_SET)은 해당 위치로 파일 위치지정자를 돌림.

C코드 최적화

profile
GGG

0개의 댓글