반응형

CMake : 공식예제 step1 해보기


MS의 VS 시리즈 통합개발환경을 사용하다가 리눅스 같은 유닉스 계열에서 비통합개발환경에서 여러 소스파일을 컴파일을 할 경우 이를 도와주는 도구가 shell, makefile, CMake 등이 있다.

 소스파일이 많아지고 복잡해질 수록 shell 보다는 makefile, makefile보다는 CMake가 비교적 적합하다. 특히 CMake의 경우 윈도우에서도 빌드할 수 있기 때문에, 소스코드 생성시 의 운영체제의 고유 API를 사용하지 않았다면, 동일한 소스파일을 빌드할 수 있다. 이를 이용해서 OpenCV등 오래전 부터 지속된 오픈소스 프로젝트들이 CMake를 사용해서 사용되어져 왔다.


 문제는 이러한 강력한 기능이 있음에도 불구하고 튜토리얼 문서는 관리가 안되고 있는 듯하다. 이 글을 기록하는 시점에 3.5.1버전이 필자의 리눅스에 설치되어 있지만, 공식 홈페이지의 메인 튜토리얼에는 여전히 2.6 버전으로 되어 있다. 게다가 상당히 난잡하게 기록되어 있다. 아마도 이를 통해서 진입장벽을 만들고 싶었던건지 아니면, 기록이 귀찮았던건지는 물어보지 않아서 알 수 없다.


 일단은 예시 들을 진행을 해보면서, 발생되는 문제점을 해결하면서 기록을 하고자 한다.


CMake 개요

 cmake는 CMakeLists.txt 명으로 된 파일을 스크립트로 인식하여 makefile을 생성하여 최종적으로는 makefile(실행은 make)을 사용하여 실행 및 공유파일을 생성한다.




예시 소스코드


// tutorial.cxx

#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include "TutorialConfig.h"

int main(const int argc, const char * argv[])
{
    double inputValue;
    double outputValue;
    if(argc < 2)
    {
        fprintf(stdout, "usage: %s number\n", argv[0]);
        return 1;
    }
    inputValue = atof(argv[1]);
    outputValue = sqrt(inputValue);
    fprintf(stdout, "The square root of %g is %g\n",
        inputValue, outputValue);

    return 0;
}


 확장자가 cxx이지만 헤더파일을 살펴보면 c인것을 알 수 있다. "TutorialConfig.h"은 CMake에서 설정하는 파일로 빌드 직전에 생성된다.



예시 헤더파일


// TutorialConfig.h.in

// the configured options and settings for Tutorial
#define Tutorial_VERSION_MAJOR @Tutorial_VERSION_MAJOR@
#define Tutorial_VERSION_MINOR @Tutorial_VERSION_MINOR@


"@"로 감싼 부분은 CMake 파일에서 후에 정의 된 값이 들어가서 작성하게 된다.



예시 CMakeLists.txt


# CMakeLists.txt

cmake_minimum_required(VERSION 2.6)
project(Tutorial)
# The version number
set(Tutorial_VERSION_MAJOR 1)
set(Tutorial_VERSION_MINOR 0)

# configure a header file to pass scma of the CMake settings
# to the source code
configure_file(
    "${PROJECT_SOURCE_DIR}/TutorialConfig.h.in"
    "${PROJECT_BINARY_DIR}/Tutorialconfig.h"
)

# add the binary tree to the search path ofr include files
# so that we will find TutorialConfig.h
include_directories("${PROJECT_BINARY_DIR}")

# add the executedable
add_executable(Tutorial tutorial.cxx)


만약에 여기 까지 진행을 했는데, math.h에 관련된 에러가 발생한다면, 위의 마지막줄 add_executable() 직전에 한줄을 추가 해주자


# add link libraries
LINK_LIBRARIES(m)

주석에 표기 되어 있는대로 라이브러리를 추가 시켜준다. 원래는 cmake가 알아서 추가 시켜줘야 하는데, 간혹 안되는 경우가 있으니 참고 하도록 하자. 위의 한줄은 math.h를 include 했을 때 컴파일 과정중 링크 단계에서 -lm을 하는 것과 같은 효과가 있다.


CMake에서는 콤마(,)가 없다는 것을 주의 해야 한다.

project(Tutorial)은 이름 그대로 프로젝트 이름을 정의해준다.


 set() 키워드는 CMake내에서 변수를 정의 혹은 초기화를 해준다. set(Tutorial_VERSION_MAJOR 1)이라고 하면, Tutorial_VERSION_MAJOR이라는 변수 없다는 생성해서 초기화를 시킨다.


add_executeable(Tutorial tutorial.cxx)는 결과 파일이름이 Tutorial로 되고 여기에 포함되는 소스가 위에 열거가 된다. 예제에서는 하나의 소스파일이지만, 실제로는 여러개의 소스파일이 사용된다.


참조 자료

공식 튜토리얼(영문)

개발자 블로그



반응형

'각종 툴' 카테고리의 다른 글

[PyCharm]기본 키 맵 해제  (0) 2020.12.20
CMake 익히기 : 기초문법 (2)  (0) 2018.02.21
CMake 익히기 : 기초문법 (1)  (0) 2018.02.19
반응형

C에서 간단한 dll을 만들어 C++에서 사용하기


 C에서는 MVC++의 C++와 달리 "#pragma" 매크로를 지원하지 않는다. 하지만, C에서는 전통적으로 다른 파일에서 사용할 수 있도록 하는 "extern"을 키워드가 있다.


 다음은 MSDN의 간단한 예시를 조금 수정했다. 


내보내기 예시)

 MSDN의 예제의 경우 __declspec( dllexport )로 작성된 부분이 __declspec( dllimport )로 작성이 되어 있는데, MSDN측 오타로 생각된다.

// MyCFuncs.h
#ifdef __cplusplus
extern "C" {  // only need to export C interface if
                  // used by C++ source code
#endif

__declspec( dllexport ) void Hello(void);

#ifdef __cplusplus
}
#endif


// MyCFuncs.c
#include "MyCFuncs.h"
#include <stdio.h>

void Hello(void)
{
    puts("Hello world");
}


dll 들여오기(Import) 공통 작업

 C++의 dll 파일을 링크하는 것과 비슷한 준비 작업이 필요하다. 실행 프로그램의 프로젝트를 생성한다. 여기서는 "Windows 데스크탑"의 빈프로젝트로 생성한다.

해당 프로젝트 폴더로 내보내는 예시에서 만든 lib파일과 헤더파일을 복사한다.

"솔루션 탐색기"에서 "헤더 파일"항목에 마우스 우클릭을 한뒤 "추가 -> 기존항목"에서 복사한 헤더파일을 추가한다.



들여오기 예시1)

 "솔루션 탐색기"에서 해당 프로젝트 명을 우클릭하여 "프로젝트 속성" 창을 연다.

프로젝트 속성의 "링크 -> 일반"의 "추가 라이브러리 디렉터리" 항목에 lib 파일이 있는 폴더 경로를 추가 입력해준다.

프로젝트 속성의 "링크 -> 입력"의 "추가 종속성"항목에 lib파일 이름과 확장자 명을 추가 입력을 한다.

아래의 소스코드를 작성한다.

// MyCFuncsRef.cpp
#include "MyCFuncs.h"

int main(const int & argc, const char * argv[])
{
    Hello();
    return 0;
}


컴파일 후 프로그램을 실행할때에는 실행하는 폴더안에 해당 dll 파일이 반드시 있어야한다.



들여오기 예시2)

아래와 같이 매크로 "#pragma comment(lib, " ./MyCfuncs.lib")를 선언부 입력을 해준다.


// MyCFuncsRef.cpp
#pragma comment(lib, "./MyCFuncs.lib")

#include "MyCFuncs.h"

int main(const int & argc, const char * argv[])
{
    Hello();
    return 0;
}


컴파일 후 실행파일이 있는 폴더 안에 해당 dll 파일이 있어야 정상적으로 실행된다.



정리

 C의 경우 클래스와 같은 자료형을 지원하지 않기 때문에 단지 헤더파일 부분만 주의 깊게 작성하면 된다. 다만, C++에서는 해당 헤더파일이 C++에서 사용된다는 것을 알리기 위하여 매크로 변수 __cplusplus를 이용 하여 extern "c" { 항목을 활성 혹은 비활성 한다는 점을 기억하자.


 dll참조 함수의 성능을 높이기 위해 포인터로 함수 주소를 가져와서 호출하는 방법도 있는데, 이부분은 추가로 더 조사를 해봐야 될것 같다.



참고자료

MSDN C 함수를 ~ 내보내기




반응형
반응형

리눅스 C : TCP 소켓 프로그램

알게된 배경

 라즈베리 파이를 위한 소캣 프로그램을 만들다 보니 어쩔 수 없이 TCP와 같은 소켓 프로그램을 제작하게 되었다. 소켓 프로그램의 경우 대부분 표준화가 되어 있으며, 구현하는 절차가 거의 정해져 있다. 때문에 이해를 했다 하더라도 외우고 있는건 무리기 때문에 기록을 남긴다.


사전에 염두 해야 할 점

 C와 C++이 일반적으로 빠른 이유는 운영체제와 거의 직접적으로 맞닿아서 작동이 된다는 점이다. 심지어 운영체제가 없는 마이크로프로세서 분야에서도 C가 사용되고 있다. 즉, 하드웨어나 운영체제의 영향을 많이 받기 때문에 당연히 윈도우와 리눅스간 코드는 차이가 있다.

 하나 더 알아야 할 것은 리눅스, OS X, FreeBSD 등 윈도우를 제외하고는 대부분은 유닉스 기반이라 소켓 코드가 거의 같다(게다가 컴파일러도 거의 비슷하다).


소스코드

 일반적으로 에코서버 코드가 기본이지만, 여기서는 스트림을 전송하거나 수신하는 코드는 제외한다.


서버

#include <sys/socket.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>

void errorHandl(char* msg)
{
    puts(msg);
    exit(1);
}

int main(int argc, char* argv[])
{
    int serv_sock;
    int clnt_sock;
    struct sockaddr_in serv_addr;
    struct sockaddr_in clnt_addr;
    socklen_t clnt_addr_sz;

    if(argc != 2)
    {
        printf("Usage : %s <port>\n", argv[0]);
        exit(1);
    }
    // 서버 TCP 소켓 생성
    serv_sock = socket(PF_INET, SOCK_STREAM, 0);
    if(serv_sock == -1)
         errorHandl("socket() error");

    memset(&serv_addr, 0, sizeof(serv_addr));
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    serv_addr.sin_port = htons(atoi(argv[1]));

    if(bind(serv_sock, (struct sockaddr*) &serv_addr, sizeof(serv_addr)) == -1)
        errorHandl("bind() error");

    // serv_sock 다음 인수는 최대 대기중인 클라이언트 수
    if(listen(serv_sock, 5) == -1)
        errorHandl("listen() error");

    clnt_addr_sz = sizeof(clnt_addr);
    clnt_sock = accept(serv_sock, (struct sockaddr*) &clnt_addr, &clnt_addr_sz);
    if(clnt_sock == -1)
        errorHandl("accept() error");

    // 이제 소켓을 통해서 전송할 작업을 작성하면 된다.
    // 생략

    // 클라이언트/서버 순서대로 스트림을 닫느다.
    close(clnt_sock);
    close(serv_sock);

    return 0;
}


클라이언트

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>

void errorHandl(char* msg)
{
    puts(msg);
    exit(1);
}

int main(int argc, char* argv[])
{
    int sock;
    struct sockaddr_in serv_addr;
    if(argc != 3)
    {
        printf("Usage : %s <IP> <port>\n", argv[0]);
        exit(1);
    }
    // TCP 소켓 설정
    sock = socket(PF_INET, SOCK_STREAM, 0);
    if(sock == -1)
         errorHandl("socket() error");

    memset(&serv_addr, 0, sizeof(serv_addr));
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_addr.s_addr = inet_addr(argv[1]);
    serv_addr.sin_port = htons(atoi(argv[2]));

    if(connect(sock, (struct sockaddr*) &serv_addr, sizeof(serv_addr)) == -1)
        errorHandl("connect() error");

    // 여기서 전송이나 수신하는 내용을 작성함
    // 생략

    // 소켓 스트림 닫음
    close(sock);

    return 0;
}



반응형
반응형

C, C++ : 운영체제를 구분할 수 있는 매크로변수

알게된 배경

 규모가 큰 프로젝트의 경우 운영체제별로 빌드되는 파일이 달라야 하기 때문에 이를 구분하기 위해서 매크로 변수를 정의하여 구분을 하고 있다.


원리

 컴파일러가 컴파일을 하기 전에 매크로를 먼저 실행을 하는데, 이 때 각 컴파일러는 환경에 대한 매크로변수가 정의 되어 있다. 이 매크로변수의 존재 유무를 가지고 운영체제를 판단하는 것이다. 단, 주의 할 점은 컴파일러자신이 있는 운영체제가 반영된 것이 때문에 윈도우에서 빌드한것은 윈도우에서 실행되고 리눅스에서 빌드된것은 리눅스에서 실행된다. 대신 소스코드만 같은 파일을 사용할 수 있다는 점이다.(그리고 이를 핑계로 고용주에게 운영체제 사달라고 하겠지..)

 실제로 규모가 큰 프로젝트는 C보다는 C++에서 많이 진행되므로 실제 사용하는 경우는 C++에서 사용될 것이다.


매크로 변수들

// windows
// 32 bit, 64 bit
#ifdef _WIN32
#endif
// only 64 bit
#ifdef _WIN64
#endif

// unix
#ifdef unix
#endif
#ifdef __unix
#endif
#ifdef __unix__
#endif

// Mac OS X
#ifdef __APPLE__
#endif
#ifdef __MACH__
#endif

// Linux
#ifdef __linux__
#endif
#ifdef linux
#endif
#ifdef __linux
#endif

// FreeBSD
#ifdef __FreeBSD__
#endif


참고자료

스택오버플로워 답변들 중


반응형

'C++' 카테고리의 다른 글

gcc C++ : 리눅스 라이브러리 만들기 입문  (0) 2017.06.12
STL C++ : vector 중복원소 제거  (0) 2017.04.28
STL C++ : string을 이용한 뒷단어 검사  (0) 2017.04.24
C++ : 네임스페이스  (0) 2017.04.21
표준 레퍼런스  (0) 2017.02.18
반응형

리눅스/윈도우 C : 현재날짜와 시간 가져오기

알게된 배경

 서버애서 생성하는 파일명에 날짜와 시간요소를 넣어서 생성하기 위해서 시스템의 시간을 알아야 할 필요가 있었다.


리눅스 계열(유닉스)과 윈도우

 윈도우 엄밀하게 말하면, MVC(VS 20xx)에서 사용하는 컴파일러는 C++에 대해서는 지원을 잘하지만, C에대해서는 지원이 잘 안되고 있다. 따라서 본의 아니게 소스코드를 실행할 때 차이점이 발생한다. 물론 이를 해결하기 위해서는 새로 소스코드를 작성하거나 minGW, Cygwin 처럼 리눅스 함수를 윈도우에 맞게 포팅해주는 컴파일러를 사용하면된다(하지만 socket 계열은 지원이 안되는 걸로 기억한다).

 시간에 대해서 더 자세히 알기 위해서는 struct tm에 대해서 검색을 해보도록 하자.



리눅스 소스코드

 C++에서는 헤더파일을 ctime으로 호출이 가능하며, 동일하게 사용이 가능하다.

// clock.c
#include <stdio.h>
#include <time.h>

void printTime(void)
{
    time_t timer;
    struct tm* t;

    timer = time(NULL);
    t = localtime(&timer);

    printf("UNIX Time : %d [sec]\n\n", timer);
    printf("year : %d\n", t->tm_year + 1900);
    printf("month : %d\n", t->tm_mon + 1);
    printf("days : %d\n", t->tm_mday);
    printf("hour : %d\n", t->tm_hour);
    printf("min : %d\n", t->tm_min);
    printf("sec : %d\n", t->tm_sec);
}


윈도우 소스코드

 리눅스 소스코드와 차이점은 struct tm 포인터 객체 대신 객체 자체를 선언하면 되며, localtime_s()를 사용한다.

// clock.c
#include <stdio.h>
#include <time.h>

void printTime(void)
{
    time_t timer;
    struct tm t;

    timer = time(NULL);
    localtime_s($t, &timer);

    printf("WINDOWS Time : %d [sec]\n\n", timer);
    printf("year : %d\n", t->tm_year + 1900);
    printf("month : %d\n", t->tm_mon + 1);
    printf("days : %d\n", t->tm_mday);
    printf("hour : %d\n", t->tm_hour);
    printf("min : %d\n", t->tm_min);
    printf("sec : %d\n", t->tm_sec);
}


참고자료

블로그: 유닉스 현재시간 아는 방법 소개


반응형

'C' 카테고리의 다른 글

리눅스 C : TCP 소켓 프로그램  (0) 2017.05.24
Linux C : 파일이나 폴더 조회하기  (0) 2017.04.24
C : 구조체를 네임스페이스처럼 사용하기  (0) 2017.04.22
C : 문자열 숫자 변환  (0) 2017.04.21
C : extern 키워드  (0) 2016.11.28
반응형

Linux C : 파일이나 폴더 조회하기

알게된 배경

 라즈베리 파이의 라즈비안에서 프로그램으로 파일관리를 하기 위해서 사용하였다. 물론 소스코드 작성은 우분투에서 작성하여 먼저 테스트를 진행 했었다.


소스 코드

// Ls.h
#include <stdio.h>
#include <dirent.h>

typedef enum {false, true}bool;
bool isFile(const unsigned char  type);
bool isFolder(const unsigned char type);
void ls(const char* path, const bool isFile);
// Ls.c
#include "Ls.h"

bool isFile(const unsigned char type)
{
    return type == 0x08;
}
bool isFolder(const unsigned char type)
{
    return type == 0x04;
}
// 실제 파일이나 폴더리스트를 가져오는 함수
// 인자는 1:초기 경로, 2:true일경우 파일명, false일경우 폴더명
void  ls(const char* path, const bool isFile)
{
    DIR* dir;
    struct dirent* ent;
    if(NULL != (dir = opendir(path)) )
    {
        while(NULL != (ent = readdir(dir)) )
        {
            if(isFile? isFile(ent->d_type) : isFolder(ent->d_type) )
            {
                printf("%s\n", ent->d_name);
            }
        }
        closedir(dir);
    }
    else
    {
        perror("could not open directory");
    }
}


추가 참조 사항

 더 궁금한 점이 있다면, dirent 구조체에 대해서 조사하도록 하자. dirent.h 파일은 디렉토리나 같은 파일 시스템과 관련된 함수들이 있다.

 C++에서는 cdirent가 없기 때문에 C의 함수를 가져와서 사용해야 한다. 다른 대체 방법이 있는 걸 아시는 분은 댓글로 피드백을 준다면 환영한다.


참고자료

커뮤니티: 파일 및 폴더 명 받기

블로그: dirent 구조체에 대한 설명

반응형
반응형

C : 구조체를 네임스페이스처럼 사용하기

알게된 배경

리눅스 시스템에서 네트워크 프로그래밍을 공부할 때 참조한 책이 하필 C로 설명이 되어 있는 책이었다. 덕분에 C++과 달리 요즘에는 자료를 찾기 힘들기 때문에 필요한 정보를 처리하기 위해서 직접 함수를 만들어야 하는 경우가 많았다.

 함수가 많아지면 발생되는 문제는 역시 작명문제이다. 그러나 C는 네임스페이스나 클래스 기능이 제공되지 않기 때문에 구조체(struct)와 포인터의 조합으로 비슷하게 흉내내서 구현할 수 있다.

 그러나 타이핑 해야할 양이 급증하게 되므로 가능하면, C로 코딩해야 하는 상황을 최대한 벗어나길 바란다.


간략한 원리

구조체가 전역으로 선언이 되어 있어야 하며, 함수들은 포인터로 연결되어 있어야 한다. 그리고 이렇게 선언된 구조체는 외부 참조가 가능하도록 선언을 해야 네임스페이스 혹은 클래스처럼 사용할 수 있다.


구현 예시

예시로 문자열의 뒤쪽의 단어가 일치하는지 검사하는 함수를 구조체내의 함수로 구현한다. 확장자가 포함된 파일이름의 확장자 검사하는 용도로 사용될 수 있다.

헤더파일)

// ns_util.h
#pragma once

typedef enum{ false, true } bool;

struct ns_util_tools
{
    bool (*isHasBackword(const char* str, const char* backword);
};
// 아래의 struct가 존재하니 이 헤더파일을 인클루드 하면 사용할 수 있음
extern const struct ns_util_tools ns_util;

C파일)

// ns_util.c
#include <string.h>
#include "ns_util.h"
bool ns_util__isHasBackword(const char* str, const char* backword)
{
    int str_len, backword_len;
    int i;
    str_len = strlen(str);
    backword_len = strlen(str);
    for(i=0; i<backword_len; ++i)
    {
        if(str[str_len -i] != backword[backword_len -i])
            return false;
    }
    return true;
}
// 실제로 네임스페이스 처럼 사용될 구조체
const struct ns_util_tools ns_util = {
    .isHasBackword = ns_util__isHasBackword
};


다른파일에서 호출)

// main.c
#include <stdio.h>
#include "ns_util.h"

int main(int argc, char* argv[])
{
    char* str = "main.exe";
    if(ns_util.isHasBackword(str, ".exe"))
        printf("%s is exe file!!", str);
    else
        printf("%s is What is?", str);

    return 0;
}



 이로써 C로써도 충분히 객체지향으로 코딩이 가능하다는 것을 알 수 있다. 그렇지만, C로 프로젝트 진행하는 것은 가능하면 피하고 싶다.

반응형
반응형

C : 문자열 숫자 변환

작성계기

이전부터 정리를 하려 했으나 게으름으로 이제야 정리를 하게 되었다. 문자열을 숫자로 바꿔야 하는 경우는 굉장히 많기 때문에 유용하다. 한편으로는 간단하게 직접 똑같은 기능을 하는 함수를 간단히 만들 수 있다(본인은 atoi()를 늦게 알게 되어서 필요할때 직접 구현을 하였었다.


atoi()

헤더 파일: stdlib.h

선언 구조: int atoi(const char* str);

리턴: 정상적으로 작동될 경우 정수를 반환하고, 실패할 경우 아무것도 반환하지 않는다.

부가 설명: 함수 이름은 a는 문자열, i는 int의 첫 글자를 따서 문자열을 정수로 바꾼다는 의미로 지어진 이름이다. 따라서 atoi() 역이 되는 함수는 itoa()가 되어야 한다.


직접 구현해보기

atoi()와 같은 기능을 하는 함수를 간단히(?) 다음과 같이 구현할 수 있다.


// 한문자를 정수 숫자로 반환하는 함수
#include <string.h>

int char2int(const char ch)
{
    char index[10] = "0123456789";
    int t;
    for(t=0; t<10; ++t)
    {
        if(index[t] == ch)
            return t;
    }
    return -1;
}

// 10의 제곱수를 반환하는 함수
int pow10(const int n)
{
    int tmp;
    int num = 1;
    for(tmp = n; tmp>0; --tmp)
        num *= 10;
    return num;
}

// atoi()와 같은 역할을 하는 함수
int str2int(const char* str)
{
    int strLen = strlen(str);
    int op = 1;          // 부호
    int n = strLen;    // 자리수
    int num = 0;       // 결과값
    int t = 0;
    for(t=0; t<strlen; ++t)
    {
        int tmp = 0;
        if(0== t && '-' == str[0])
        {
            op = -1;
            --n;
            continue;
        }
        if(-1 == tmp)
            return;
        num += tmp*pow10(n);
    }
    return num*op;
}

조금 차이지만 여전히 C++보다 C가 더 불편하니 가능하면, 표준함수에서 제공하는 것을 소중히 여기도록 하자.

반응형
반응형

C : extern 키워드

작성계기

실제로 실수를 했었고, 찾아보니 의외로 다른사람도 잦은 실수를 하는 것 같아서 작성함.


키워드의 의미

다른 파일에 있는 변수를 현재 파일에서 사용하기 위해서 사용하는 키워드이다. 원리상 다른 파일 어딘가(메모리의 어딘가)에 해당변수가 있으니 그걸 참조하라는 의미가 된다.


예시)

a.c (변수가 정의 되어 있는 파일)

int a = 0;


b.c (a.c 파일에 있는 변수를 호출해서 사용하는 파일)

extern int a;
a = 20;


활용 및 응용

매번 새로운 파일에서 참조를 할 때마다 앞단에 extern 키워드로 사용가능하게 하는 것은 상당히 번거롭기 때문에 해당되는 헤더파일에 작성하여 여러번 작성을 하지 않게 할 수 있다.


예시)

a.c (변수가 정의 되어 있는 파일)

#include "a.h"
int a = 0;


a.h (변수가 정의 되어 있는 파일의 헤더파일)

#pragma once
extern int a;


b.c (a.c 파일에 있는 변수를 사용하는 파일)

#include "a.h"
a = 20;


당연한 말이지만, 해당 선언을 중복으로 선언하게 되면, 오류가 된다. 따라서 #pragma once 혹은 #ifndef를 활용하여 중복선언을 피하도록 하자.


실수하는 예

C의 전형적인 함정은 주로 배열에서 발생된다. 배열로 선언/정의 한것과 포인터로 선언/정의 된것은 다른 것으로 컴파일러가 구분한다. 의미상 배열선언의 키워드도 포인터로 같은 종류로 (사람은) 인식하지만, 컴파일러는 다른 것으로 인식을 한다.


예시)

a.c (정의된 원본 파일)

char a[] = "abc";


b1.c (오류/에러로 인식 "그런거 없다고 한다")

extern *a;
char b = a[0];


b2.c (정상 호출)

extern a[];
char b = a[0];



반응형
반응형

리눅스/윈도우 C : 헤더파일 중복 참조 방지

알게된 배경

 참여하는 프로젝트에서 소켓 네트워크 프로그램을 만들어야 하는데, 가장 먼저 잡은 책이 하필 C로 실습하는 책이었다. 문제는 C는 운영체제를 많이 타는 편이라서 리눅스와 윈도우를 같이 번갈아가면서 사용해야 했다.


들어가기에 앞서

 현재 C도 C++처럼 표준이 존재하며, 리눅스는 C90 ~ C99를 지원하고, 윈도우는 C80만 지원한다. 따라서 지속적으로 계속 차이가 날 수 밖에 없다. 그리고 대부분의 C 학습 서적은 대부분 윈도우 환경의 Microsoft Visual(MV) C++ 컴파일러를 기준으로 되어 있다.


매크로변수로 구분하는 방법(#ifndef)

예시)

#ifndef _HEADER_H_
#define _HEADER_H_

int func1(void);

#endif //_HEADER_H_

초기 C에서 사용되는 방법으로 매크로 변수 선언여부에 따라서 컴파일러는 정해진 구간의 코드를 컴파일하지 않는다. 매크로는 컴파일 하기전에 실행되는 것이기 때문에 이를 통해서 매번 다른 C파일에서 중복으로 헤더파일을 인클루드 해도 컴파일러는 여러번 참조하지 않는다. 헤더파일 중복 참조를 막기 위한 변수명은 위의 코드처럼 _(언더바)로 시작하고 대문자로 작성하며, 마지막에 _H_로 표기하도록 권장한다.


 다만, 기기 입장에서 컴파일을 할때 매크로에 의해서 컴파일전에 헤더파일의 내용을 건너뛰기는 하지만, 파일 전체가 아닌 구간을 넘기는 거라서 원리상 파일을 중복 참조하기는 한다. 그렇지만, 이게 워낙 빨라서 사람이 체감하기는 어렵다.


한번만 읽는 매크로를 실행하는 방법(#pragma once)

#pragma once

int func1(void);

MV C++ 에서 사용하기 시작한 방법이다. 매크로는 컴파일러가 컴파일전에 작업한다는 것을 다시 한번 상기하자. 매크로 명령어로 컴파일러에게 컴파일전에 이 파일은 한번만 읽으라고 하는 의미이다. 결국 앞의 매크로변수 유무에 따라서 컴파일하는 것과 같은 결과를 갖게 되는데, 원리상 차이가 있다.


 파일을 한번만 읽으라고 했기 때문에 아무래도 매크로 변수 존재 유무를 확인하고 구간을 건너뛰는 것보다는 매크로실행과정이 짧아진다. 물론 속도 차이는 체감이 거의 안된다.


중복참조 선택방법

 이러한 매크로(#pragma once)는 처음에 MV C++에서만 지원이 되었고 비표준이었다. 하지만, 요즘에는 표준화가 되었다. 그러나 아직 표준화 된지 오래 안되어서 인지 지원이 안되는 컴파일러가 있을 수 있기에 아직도 매크로 변수를 이용한 중복 참조를 피하는 방법을 리눅스 프로그래밍(OS X 에서도) 쪽에서는 아직도 많이 사용하고 있다.


 자신의 개발환경이 오래된 혹은 가벼운 컴파일러를 사용해야 할 경우에는 어쩔 수 없이 매크로변수를 이용해야 하고 그게 아니라면 #pragma once를 사용하자.


위키피디아에서 #pragma once 매크로가 지원되는 컴파일러 목록이 정리되어 있다

[참조링크]


반응형

+ Recent posts