extern “C”의 사용

Reference
기본 code
/* add.h */
int add(int a, int b);
/* add.c */
int add(int a, int b)
{
    return a + b;
}
/* hello.cpp */
#include <stdio.h>

#include "add.h"

int main(void)
{
    printf("hello world\n");
    add(3,5);

    return 0;
}

hello.cpp의 add 심볼을 보면, c++ 형태로 되어 있다.

Jungguui-MacBook-Pro:sand_project hyde1004$ g++ -c hello.cpp 
Jungguui-MacBook-Pro:sand_project hyde1004$ nm hello.o
0000000000000070 s EH_frame0
000000000000003f s L_.str
                 U __Z3addii
0000000000000000 T _main
0000000000000088 S _main.eh
                 U _printf

add.c의 add 심볼 역시, c++ 형태이다.

Jungguui-MacBook-Pro:sand_project hyde1004$ g++ -c add.c 
clang: warning: treating 'c' input as 'c++' when in C++ mode, this behavior is deprecated
Jungguui-MacBook-Pro:sand_project hyde1004$ nm add.o
0000000000000038 s EH_frame0
0000000000000000 T __Z3addii
0000000000000050 S __Z3addii.eh

add.c를 다음과 같이 수정하고, 심볼을 살펴보면 c형태로 되어 있음을 확인할 수 있다.

/* add.c */
#ifdef __cplusplus
extern "C" {
#endif

int add(int a, int b)
{
    return a + b;
}

#ifdef __cplusplus
}
#endif
Jungguui-MacBook-Pro:sand_project hyde1004$ g++ -c add.c 
clang: warning: treating 'c' input as 'c++' when in C++ mode, this behavior is deprecated
Jungguui-MacBook-Pro:sand_project hyde1004$ nm add.o
0000000000000038 s EH_frame0
0000000000000000 T _add
0000000000000050 S _add.eh

현재 상태에서 링크를 시도하면, hello.o와 add.o의 심볼이 달라서 에러가 발생한다. hello.o의 add()는 c++형태이고, add.o는 c형태이다.

Jungguui-MacBook-Pro:sand_project hyde1004$ g++ hello.o add.o 
Undefined symbols for architecture x86_64:
  "add(int, int)", referenced from:
      _main in hello.o
ld: symbol(s) not found for architecture x86_64
clang: error: linker command failed with exit code 1 (use -v to see invocation)

다음과 같이 hello.cpp를 수정하고 다시 링크를 해보자. 정상적으로 링크가 완료된다. hello.cpp의 extern "C"를 add.h로 옮기는 것도 괜찮을 것 같다.

/* hello.cpp */
#include <stdio.h>

#ifdef __cplusplus
extern "C" {
#endif

#include "add.h"

#ifdef __cplusplus
}
#endif

int main(void)
{
    printf("hello world\n");
    add(3,5);

    return 0;
}
Jungguui-MacBook-Pro:sand_project hyde1004$ g++ -c hello.cpp 
Jungguui-MacBook-Pro:sand_project hyde1004$ nm hello.o
0000000000000070 s EH_frame0
000000000000003f s L_.str
                 U _add
0000000000000000 T _main
0000000000000088 S _main.eh
                 U _printf
Jungguui-MacBook-Pro:sand_project hyde1004$ g++ add.o hello.o

'C' 카테고리의 다른 글

CppUTest  (0) 2014.05.26
Unity를 이용한 TDD - 2  (0) 2014.04.13
volatile  (0) 2014.04.09
Unity를 이용한 TDD  (0) 2014.04.02
[도서] 쉽게 배우는 C프로그래밍 테크닉 - 2.2 헤더 파일 만들기  (0) 2014.03.04

Unity를 이용한 TDD - 2

References
  1. Unity 의 examples/example_2

이번 글에서는 이전 편과는 다른 순서로 진행한다. 이전 편에서는 함수를 만들고, Test Case를 구성하고, Test Runner 순으로 진행했으나, 이번 편에서는 반대순으로 진행해본다. 왜냐하면 Test Runner을 만드는 관점을 충분히 이해하지 못했기 때문이다.

/* all_test.c */
#include "unity_fixture.h"

static void RunAllTests(void)
{
    RUN_TEST_GROUP(add);
    RUN_TEST_GROUP(abstract);
}

int main(int argc, char * argv[])
{
    return UnityMain(argc, argv, RunAllTests);
}

all_test.c는 Unity를 실행해주는 main( )이다. RunAllTests( )에는 Test Group을 나열해 준다. Test Group은 동일한 모듈을 테스트할 Test Cases을 모아놓은 Container이다. 여기서는 add와 abstract을 Test한다.

/* TestAdd_Runner.c    */
#include "unity.h"
#include "unity_fixture.h"

TEST_GROUP_RUNNER(add)
{
    RUN_TEST_CASE(add, positive_add);
    RUN_TEST_CASE(add, negative_add);
}

TEST_GROUP_RUNNER(abstract)
{
    RUN_TEST_CASE(abstract, positive_abstract);
    RUN_TEST_CASE(abstract, negative_abstract);
}

TestAdd_Runner.c는 Test Runner이다. (원래는 add만 있었음으로 파일이름이 TestAdd_Runner.c였으나, 나중에 abstract을 추가함) 여기는 main( )에 있던 Test Group에 Test Case을 등록하는 것이다. 즉, Test Group에 따른 모든 Test Case를 적어두는 것이다. 이전의 example_1과 비교하면 Test Runner만드는 법이 매우 쉽다. 또한 ruby script를 쓸 필요도 없다. 다만, 만약 여기에 포함되지 않은 Test Case가 있다면, Test되지 않는다. 따라서, Test Case를 추가했다면, 반드시 TEST_GROUP_RUNNER에 추가해주어야 한다.

/* TestAdd.c */
#include "add.h"
#include "abstract.h"
#include "unity.h"
#include "unity_fixture.h"

TEST_GROUP(add);

TEST_SETUP(add)
{

}

TEST_TEAR_DOWN(add)
{

}

TEST(add, positive_add)
{
    TEST_ASSERT_EQUAL(3, add(1,2));
    TEST_ASSERT_EQUAL(7, add(3,4));
    TEST_ASSERT_EQUAL(43, add(10, 33));
}

TEST(add, negative_add)
{
    TEST_ASSERT_EQUAL(-2, add(-1,-1));
    TEST_ASSERT_EQUAL(-4, add(-3, -1));
    TEST_ASSERT_EQUAL(-12, add(-10,-2));
}

TEST_GROUP(abstract);

TEST_SETUP(abstract)
{

}

TEST_TEAR_DOWN(abstract)
{

}

TEST(abstract, positive_abstract)
{
    TEST_ASSERT_EQUAL(5, abstract(8,3));
    TEST_ASSERT_EQUAL(8, abstract(10,2));
    TEST_ASSERT_EQUAL(2, abstract(10,8));
}

TEST(abstract, negative_abstract)
{
    TEST_ASSERT_EQUAL(-3, abstract(5, 8));
    TEST_ASSERT_EQUAL(-10, abstract(10, 20));
    TEST_ASSERT_EQUAL(-7, abstract(10, 17));
}

TestAdd.c (처음에는 add Test Case만 있었으나, abstract을 추가)에는 Test Case를 서술한다. 예제에서는 add와 abstract을 별도 함수로 분리하였으나, 여기 예제처럼 하나의 파일로도 가능하다.
모든 Test Group에서는 TEST_SETUP( ), TEST_TEAR_DOWN( )은 반드시 있어야 하며 그렇지 않으면 컴파일 에러가 발생한다.

다음은 test를 위한 source이다.

/* add.c */
#include "add.h"

int add(int a, int b)
{
    return a + b;
}
/* abstract.c */
#include "abstract.h"

int abstract(int a, int b)
{
    return a - b;
}
/* add.h */
int add(int a, int b);
/* abstract.h */
int abstract(int a, int b);

컴파일하는 법은 다음과 같다. 실제 프로젝트에 이용할때는 Makefile에 src, Test, Unity src로 분리하여 구성하는 것이 좋겠다.

gcc src/add.c                                 \
    src/abstract.char                        \
    -Isrc/include 

    -I../Unity-master/extras/fixture/src     \
    -I../Unity-master/src/                     \
    ../Unity-master/src/unity.c             \
    ../Unity-master/extras/fixture/src/unity_fixture.c     \ 

    test/TestAdd.c                             \    
    test/test_runners/TestAdd_Runner.c         \
    test/test_runners/all_tests.c

'C' 카테고리의 다른 글

CppUTest  (0) 2014.05.26
extern "C"의 사용  (0) 2014.05.26
volatile  (0) 2014.04.09
Unity를 이용한 TDD  (0) 2014.04.02
[도서] 쉽게 배우는 C프로그래밍 테크닉 - 2.2 헤더 파일 만들기  (0) 2014.03.04

volatile

References

volatile 키워드에 대해서 찾아보게 된 계기는 ‘Head First Design Patterns’의 싱글톤 패턴에서 volatile을 사용하고 있었기 때문이었다.

C언어 문법 중 완전히 이해되지 않은 부분이 volatile이었는데, 오늘에야 그 의미를 이해하게 되었다. volatile의 의미는 컴파일 최적화를 하지 않는다는 것이다. 그것은 컴파일러의 최적화 옵션과 관련이 있다.

  • 코드는 순서대로 실행되지 않는다.
  • 코드대로 기계어로 변환되지 않는다.

아래는 위키에서 가져온 코드이다.

static volatile int foo;

void bar (void)
{
    foo = 0;

    while (foo != 255);
}

사실 위 코드는 foo값이 255가 될때까지 기다린다. 그러나 volatile 키워드가 없다면, 컴파일러는 아래와 같이 최적화한다.

void bar_optimized(void)
{
    foo = 0;

    while (true);
}

사실 컴파일러 입장에서는 명백하다. foo는 0이고 변경되지 않기 때문에 항상 while은 참이 되므로, foo를 비교할 필요없다고 여기게 된다. 그러나, 실제로는 하드웨어 인터럽트등을 통해 값이 변경될 수 있다는 것이다.
‘Head First Design Patterns’에서는 멀티스레딩에 대한 내용을 서술하면서, 위의 코드에서와 비슷한 상황을 설명하고 있다. 그러나 References의 마지막 링크에서는 조금 다른 이야기를 하고 있으므로 참조할 필요가 있다.

+ Recent posts