Bus error? 버스 에러? 뭐지?

2014.07.19 07:43 컴퓨터/우분투/리눅스

프로그램을 실행해 보니 전혀 본 적이 없는 에러가 출력되네요. Bus error? 프로그램 코드를 보면 전혀 문제가 없는데, 실행만 하면 Bus error가 발생합니다. 재미있는 것을 I386 컴퓨터에서는 발생하지 않는데, Arm processor에서 발생하네요. 이 무슨 일일까?

Alignment trap: not handling instruction ed850a000 at [<00009520]
Unhandled fault: alignment exception (0x811) at 0x7ef4c685
Buf error

아래 소스 코드를 보시죠.

typedef struct{
    float   f_value1;
    char    c_value1;
    float   f_value2;
    float   f_value3;
    float   f_value4;
} __attribute__ ((packed)) t_curr_value;

static void get_float_value( float *p_value){

    char str[] = "9999.999";

    *p_value = atof( str);                 // <-- 에러가 발생하는 위치
}

int   main( int argc, char *argv[])
{
    t_curr_value curr_value;

    get_float_value( &curr_value.f_value1);
    get_float_value( &curr_value.f_value3);  // <--- Bus error!!

    return 0;
}

Arm 프로세서 보드에서 실행하면 화살표 표시한 부분에서 Bus error이 발생합니다. 더 정확히 말씀 드리면 같은 float 변수인 curr_value.f_value1를 인수로 호출할 때는 에러가 발생하지 않지만, curr_value.f_value3을 인수로 호출하면 atof() 함수 행에서 Bus error 에러가 발생합니다.

더욱 재미있는 것은 curr_value.f_value1을 호출하지 않고 curr_value.f_value3만 호출하면 에러가 발생하지 않습니다.

int   main( int argc, char *argv[])
{
    t_curr_value curr_value;

    get_float_value( &curr_value.f_value3);  // <--- 정상 실행

    return 0;
}

또는, atof() 함수를 호출하지 않고 값을 대입해 주어도 에러가 발생하지 않습니다.

static void get_float_value( float *p_value){

    *p_value = 99.99;                 // <-- 에러 안 남, 잉?
}

뭐, 이런 경우가 다 있지!!

이유를 알기 위해 부단히 고생했지만, 정확한 답을 찾지 못했습니다. 다행히 문제를 해결하는 방법은 찾았습니다. 바로 구조체에서 __attribute__ ((packed))을 제거하면 됩니다.

왜 이런 문제가 발생하는지는 앞서도 말씀 드렸듯이 정확히는 모릅니다. Arm 프로세서의 특성 때문이라는 분도 있고 컴파일러의 버그라는 분도 있는데 원인에 대한 가장 신빙성 있게 보이는 것은 속도 최적화에 따른 구조체 속의 변수 주소 때문이라는 의견입니다.

32비트 시스템에서 구조체를 선언하면 속도 최적화를 위해 4바이트 단위로 주소를 잡도록 빈 공간을 채워줍니다.

typedef struct{
    float   f_value1;
    char    c_value1;
    float   f_value2;
    float   f_value3;
    float   f_value4;
} t_curr_value;

이렇게 __attribute__ ((packed))을 사용하지 않고 구조체를 선언하면 float 변수 4개, char 변수 1개로 총 17byte 크기로 생각되지만, 실제로는 char c_value 다음에 있는 f_value2도 빠르게 접근할 수 있도록 char c_value1과 f_value2 사이에 3개의 바이트를 추가해 줍니다. 그래서 실제 t_curr_value의 크기는 20 바이트이지요.

그러나 이런 주소 최적화를 하지 않도록 __attribute__ ((packed))을 사용하면 빈 공간을 재워주는 임시 변수가 생성되지 않습니다. char c_value1 다음의 모든 float 변수는 바로 접근하지 못하고 주소 연산을 해야 합니다. 어떻게 주소 연산을 하는지는 모르지만, 문제는 char c_value1 변수 하나 때문에 f_value2, f_valu3, f_value4는 32비트 시스템에 최적화된 주소 위치가 아니라는 점입니다.

그래서 각 변수의 주소 값으로 다른 함수에 call by reference를 하면 문제가 될 수 있다는 것이죠.

또한, Arm 프로세서뿐만 아니라 RISC 칩을 사용한 시스템에서 이런 문제가 발생한다는 얘기를 보아서는 컴파일 에러라기 보다는 프로세서의 특성이라고 생각합니다. 어쩌면 프로세서의 매뉴얼에 이와 같은 내용에 대한 주의 문구가 있을지 모르겠습니다.

재미 없는 얘기를 블로그에까지 올린 이유는 이 문제로 고생하기도 했지만, 전혀 생각지도 못한 문제에 기도 막히고 해서 올립니다. 앞으로 구조체를 잡을 때는 4바이트 단위로 끊어서 배열해야겠어요. 즉,

typedef struct{
    float   f_value1;
    float   f_value2;
    float   f_value3;
    float   f_value4;
    char    c_value1;
} t_curr_value;

이렇게 말이죠. 어휴~

신고
이 댓글을 비밀 댓글로