2010-12-14 4 views
28

임베디드 시스템을 프로그래밍 할 때 매우 자주 malloc()이 허용되지 않습니다. 대부분이 문제를 처리 할 수 ​​있지만, 한 가지는 나를 괴롭 히고 있습니다. 데이터 숨기기를 위해 소위 '불투명 한 유형'을 사용하지 못하게합니다. 일반적으로 나는 같은 것을 할 것 :은폐 데이터 형식의 정적 할당

// In file module.h 
typedef struct handle_t handle_t; 

handle_t *create_handle(); 
void operation_on_handle(handle_t *handle, int an_argument); 
void another_operation_on_handle(handle_t *handle, char etcetera); 
void close_handle(handle_t *handle); 


// In file module.c 
struct handle_t { 
    int foo; 
    void *something; 
    int another_implementation_detail; 
}; 

handle_t *create_handle() { 
    handle_t *handle = malloc(sizeof(struct handle_t)); 
    // other initialization 
    return handle; 
} 

이 당신이가는 : create_handle를()는 malloc을()에 '예'를 만들을 수행합니다. 자주) (의 malloc에 ​​필요가 없도록하는 데 사용되는 건설은이 같은 create_handle (의 프로토 타입)를 변경하는 것입니다

void create_handle(handle_t *handle); 

을 그리고 호출자는 핸들 이런 식으로 만들 수 있습니다

// In file caller.c 
void i_am_the_caller() { 
    handle_t a_handle; // Allocate a handle on the stack instead of malloc() 
    create_handle(&a_handle); 
    // ... a_handle is ready to go! 
} 

그러나 불행하게도을 이 코드는 분명히 유효하지 않습니다. handle_t의 크기는 알려지지 않았습니다!

나는 이것을 올바른 방법으로 해결하는 해결책을 찾지 못했습니다. 나는 누군가가 이것을하는 적절한 방법을 가지고 있는지 또는 C에서 데이터 숨기기를 가능하게하는 완전히 다른 접근법을 알고 있는지 알고 싶다. 물론 모듈의 정적 전역을 사용하지 않는다. 물론 하나의 인스턴스를 여러 개 만들 수 있어야한다.).

+2

아마도 뭔가를 놓치고 있습니다. handle_t의 크기가 알려지지 않은 이유는 무엇입니까? "create_handle"은 "handlet_t *"유형의 인수를 취하므로 크기에 대한 지식을 가져야합니다. 배열을 통과하면 다른 문제가 될 것이라고 생각합니다. – onemasse

+4

@onemasse handle_t의 크기는 caller.c에 알려지지 않으며 handle_t에 대한 포인터 만 사용할 수 있습니다. handle_t의 크기는 module.c에서만 알 수 있습니다. – Bart

+0

@onemasse 전달 선언 및 포인터는 불투명 유형을 사용할 수 있으므로 구현이 클라이언트가 아닌 크기를 알 수 있습니다. – Ioan

답변

15

_alloca 함수를 사용할 수 있습니다. 필자는 이것이 정확히 Standard는 아니지만, 내가 아는 한 거의 모든 일반적인 컴파일러가이를 구현한다. 기본 인수로 사용하면 호출자의 스택에서 할당됩니다.

// Header 
typedef struct {} something; 
int get_size(); 
something* create_something(void* mem); 

// Usage 
handle* ptr = create_something(_alloca(get_size()); // or define a macro. 

// Implementation 
int get_size() { 
    return sizeof(real_handle_type); 
} 
something* create_something(void* mem) { 
    real_type* ptr = (real_type_ptr*)mem; 
    // Fill out real_type 
    return (something*)mem; 
} 

또한 오브젝트 풀 반 힙의 어떤 종류를 사용할 수 있습니다 - 당신이 현재 사용 가능한 객체의 최대 수있는 경우에 사람이있는, 당신은 정적으로 그들을 위해 모든 메모리를 할당 할 수 있으며, 단지 비트 이동 현재 사용 중입니다.

#define MAX_OBJECTS 32 
real_type objects[MAX_OBJECTS]; 
unsigned int in_use; // Make sure this is large enough 
something* create_something() { 
    for(int i = 0; i < MAX_OBJECTS; i++) { 
     if (!(in_use & (1 << i))) { 
      in_use &= (1 << i); 
      return &objects[i]; 
     } 
    } 
    return NULL; 
} 

내 비트 이동 나는 그것을 한 적이 있기 때문에 오랜 시간이 조금 떨어져,하지만 난 당신이 포인트를 얻을 수 있기를 바랍니다.

+0

'alloca()'는 불투명 핸들 문제를 수정하지 않습니다. 객체의 크기를 알아야 객체가 불투명해질 수 없습니다. 메모리 풀이 자주 사용됩니다. –

+0

최대 예상 크기의 개체 풀이 가장 좋은 솔루션입니다. 기본적으로 사용자 정의 할당 자로 사용합니다. 필요한 최대 개체 수를 모르는 경우 큰 수 (메모리 제한 내)를 선택하고 할당 실패를 처리하십시오. – Ioan

+1

@Michael 크기는 "sizeof (struct handle_t)"주위의 래퍼 일 수있는 get_size()로 얻습니다. alloca가 지원되지 않으면 항상 C99 가변 길이 배열을 사용할 수 있습니다. – onemasse

8

방법 중 하나는 공공 module.h 헤더에

#define MODULE_HANDLE_SIZE (4711) 

같은 것을 추가하는 것입니다. 이렇게하면 실제 크기와 동기화되는 걱정스러운 요구 사항이 생기므로 빌드 과정에서 자동으로 생성됩니다.

다른 옵션은 물론 실제로 구조를 노출하지만 불투명 한 것으로 문서화하고 정의 된 API가 아닌 다른 방법을 통해 액세스를 금지합니다. 이 같은 일을보다 명확하게 할 수 있습니다 : 여기

#include "module_private.h" 

typedef struct 
{ 
    handle_private_t private; 
} handle_t; 

는 모듈의 핸들의 실제 선언은 덜 분명히 볼 수 있도록하기 위해, 별도의 헤더에 이동되었습니다. 그 헤더에 선언 된 타입은 원하는 typedef 이름으로 간단하게 싸여지고, 이것이 private인지를 확인하도록합니다.

handle_t *을 사용하는 모듈 내부의 함수는 public struct의 첫 번째 멤버이기 때문에 privatehandle_private_t 값으로 안전하게 액세스 할 수 있습니다.

+1

"private"요소가 .c 파일에 포함 된 다른 이름으로 정의된다는 것을 의미하는 매크로를 추가 할 수도 있습니다. 그런 식으로 코드가 어떤 일을해서는 안되는 경우 (예 :'h-> do_not_use_thisfrom_anywhere_ever.num ++') 그리고 위반에 대해 grep을 약간 더 쉽게 만들 수 있습니다. – psmears

+0

이 솔루션으로 살 수는 있지만 여전히 있습니다. 단점은 구현시에만 사용되는 헤더 파일이 변경된 경우 .c 파일을 사용하여 다시 컴파일해야한다는 것입니다. 또한 .c를 사용하여 컴파일하는 경우 구현을 컴파일하는 데 동일한 포함 경로가 필요합니다. – Bart

0

malloc()을 사용할 수 없다고 말하는 이유가 약간 혼란 스럽습니다. 분명히 임베디드 시스템에서는 제한된 메모리를 가지며 일반적인 해결책은 큰 메모리 풀을 malloc하는 자체 메모리 관리자를 갖는 것입니다. 그런 다음 필요에 따라이 부분을 할당합니다.나는이 아이디어의 다양한 구현을 내 시간에 보았다.

질문에 대답하기 위해 단순히 고정 크기 배열을 module.c에 정적으로 할당하지 마십시오. "in-use"플래그를 추가 한 다음 create_handle()을 사용하여 포인터를 첫 번째 자유 요소.

"핸들"은 객체의 자체 정의에 캐스팅하여 객체를 남용하려고 시도하는 실제 포인터가 아닌 정수 인덱스가 될 수 있습니다.

+5

'malloc()'은 임베디드 시스템에서 테스트가 어렵거나 불가능한 조각화와 시나리오를 가져올 수 있기 때문에 정적 할당을 위해 종종 금지됩니다. 특히 긴 '가동 시간'요구 사항이있는 시스템의 경우. 오브젝트가 정적으로 할당되면 시스템이 빌드되면 메모리 할당이 실패 할 수 없습니다. –

+0

어쩌면 그 질문에 답할 수 있도록 질문에 넣어야합니다. 우리는 우리 시스템의 조각화에 몇 가지 문제가 있습니다. 우리는 움직일 수있는 블록의 일종의 시스템을 가진 메모리 풀 타입을 가지고 있습니다. (너무 잘 작동하지 않습니다.) 그래서 여러분은 메모리 조각 모음을 할 수 있습니다. 그러나 아무도 그걸 사용하지 않습니다. – AlastairG

+0

임베디드 시스템에서 malloc()을 사용하지 않는 또 다른 이유는 코드 크기입니다. 일반적으로 libc malloc 구현은 작지 않으며 코드를 가져 오는 많은 다른 코드를 가지고 있습니다. 코드 크기 경계에 부딪혔다면 그렇게하지 않을 것입니다. –

3

불행히도,이 문제를 다루는 일반적인 방법은 프로그래머가 객체를 불투명하게 처리하도록하는 것입니다. 전체 구조 구현이 헤더에 있고 사용할 수 있습니다. 프로그래머가 헤더를 사용하지 않고 사용하는 것은 프로그래머의 책임입니다. 객체에 대해 정의 된 API를 통해서만 직접 내부 객체를 가져올 수 있습니다. 는 '더 나은 C'로

  • 사용 C++를하고 private 같은 구조의 내부를 선언 :이 충분하지 않은 경우

    , 몇 가지 옵션이 될 수 있습니다.

  • 헤더의 사전 처리기를 실행하여 구조체의 내부를 선언하지만 사용할 수없는 이름으로 만듭니다. 좋은 이름을 가진 원래 헤더는 구조를 관리하는 API 구현에 사용할 수 있습니다. 나는이 기술이 사용 된 것을 결코 보지 못했습니다. 그것은 가능할 수도있는 머리 꼭대기에서 벗어난 아이디어 일 뿐이지 만 가치가있는 것보다 훨씬 더 문제가있는 것처럼 보입니다.
  • 불투명 포인터를 사용하는 코드는 정적으로 할당 된 객체를 extern (즉, 전역 변수)으로 선언하십시오. 그런 다음 객체의 전체 정의에 대한 액세스 권한을 가진 특수 모듈에서 실제로 이러한 객체를 선언하십시오. '특수'모듈 만 전체 정의에 액세스 할 수 있으므로, 불투명 한 오브젝트의 정상적인 사용은 불투명하게 유지됩니다. 그러나 이제는 전역 객체라는 사실을 남용하지 않기 위해 프로그래머를 의지해야합니다. 또한 이름 충돌을 변경하여 관리해야 할 필요가 있습니다 (큰 문제는 아니지만 실수로 발생할 수 있음을 제외하고 - 아!).

나는 전반적으로, 프로그래머가이 객체를 사용하기위한 규칙을 따르는 것이 가장 좋은 해결책이라고 생각합니다. (C++의 하위 집합을 사용하는 것이 나쁘지 않습니다.) 구조를 사용하지 않는 규칙을 따르는 프로그래머에 따라 내부가 완벽하지는 않지만 공통적으로 사용 가능한 실행 가능한 솔루션입니다.

4

struct handle_t 개체의 정적 풀을 만들고 그 다음에 neceessary로 제공하는 경우 하나의 솔루션입니다. 이 그것을 달성하는 방법에는 여러 가지가 있지만 간단한 설명을 예는 다음과 같습니다

// In file module.c 
struct handle_t 
{ 
    int foo; 
    void* something; 
    int another_implementation_detail; 

    int in_use ; 
} ; 

static struct handle_t handle_pool[MAX_HANDLES] ; 

handle_t* create_handle() 
{ 
    int h ; 
    handle_t* handle = 0 ; 
    for(h = 0; handle == 0 && h < MAX_HANDLES; h++) 
    { 
     if(handle_pool[h].in_use == 0) 
     { 
      handle = &handle_pool[h] ; 
     } 
    } 

    // other initialization 
    return handle; 
} 

void release_handle(handle_t* handle) 
{ 
    handle->in_use = 0 ; 
} 

, 당신은 예를 들어 각각 핸들이 할당되는 시간을 증가 정적 인덱스를 유지할 수있는 사용되지 않는 핸들을 찾는 빠른 방법은 빠른

있다 MAX_HANDLES에 도달하면 '둘러보기'됩니다. 이것은 하나의 핸들을 해제하기 전에 여러 핸들이 할당되는 일반적인 상황에서 더 빠릅니다. 그러나 핸들 수가 적 으면이 무차별 대항 탐색이 적절할 것입니다.

물론 핸들 자체는 더 이상 포인터가 아니어도 숨겨진 풀에 대한 간단한 인덱스 일 수 있습니다. 이렇게하면 외부에서 데이터를 숨기고 풀을 보호 할 수 있습니다.

typedef int handle_t ; 

과 같이 변경됩니다 코드는 다음과 없습니다 :

그래서 헤더는 것이다 반환 된 핸들이 내부 데이터에 대한 포인터가 더 이상

// In file module.c 
struct handle_s 
{ 
    int foo; 
    void* something; 
    int another_implementation_detail; 

    int in_use ; 
} ; 

static struct handle_s handle_pool[MAX_HANDLES] ; 

handle_t create_handle() 
{ 
    int h ; 
    handle_t handle = -1 ; 
    for(h = 0; handle != -1 && h < MAX_HANDLES; h++) 
    { 
     if(handle_pool[h].in_use == 0) 
     { 
      handle = h ; 
     } 
    } 

    // other initialization 
    return handle; 
} 

void release_handle(handle_t handle) 
{ 
    handle_pool[handle].in_use = 0 ; 
} 

때문에, 그리고 호기심 또는 악의적 사용자는 핸들을 통해 액세스 할 수 없습니다.

여러 스레드에서 핸들을 가져 오는 경우 일부 스레드 안전 메커니즘을 추가해야 할 수도 있습니다.

0

내가보기에 가장 어려운 해결책은 호출자의 사용을 위해 불투명 한 구조체를 제공하는 것입니다.이 구조체는 실제 구조체에 사용 된 유형에 대한 언급과 함께 충분히 크고 더하기 쉽습니다. 불투명 구조체는 진짜에 비해 충분히 잘 정렬 될 수 있도록 :

struct Thing { 
    union { 
     char data[16]; 
     uint32_t b; 
     uint8_t a; 
    } opaque; 
}; 
typedef struct Thing Thing; 

는 그런 기능이 그 중 하나에 대한 포인터를 가지고 :

void InitThing(Thing *thing); 
void DoThingy(Thing *thing,float whatever); 

내부적으로는 API의 일부로 노출되지, 진정한 내부 구조를 가진 구조체가 있습니다 :

struct RealThing { 
    uint32_t private1,private2,private3; 
    uint8_t private4; 
}; 
typedef struct RealThing RealThing; 

(이 사람은 단지 uint32_t' and uint8_t있다 '-. 즉 위의 조합에서 이러한 두 가지 유형의 모양에 대한 이유를)

플러스 아마 컴파일시 어설 RealThing 있는지 확인'

: 그런 다음 라이브러리에있는 각 함수가 인수에 캐스트를 수행
typedef char CheckRealThingSize[sizeof(RealThing)<=sizeof(Thing)?1:-1]; 

이 그것을 사용하는 것 :의 크기는 Thing의를 초과하지 않는

호출자는 스택에 올바른 크기의 객체를 만들고 함수를 호출 할 수 있습니다. 구조체는 여전히 불투명하고 불투명 한 버전이 충분히 큰지 몇 가지 검사가 있습니다.

하나의 잠재적 인 문제는 필드가 실제 구조체에 삽입 될 수 있다는 것입니다. 즉, 불투명 구조체가 정렬하지 않아야하며, 크기 검사를 수행하지 않아도된다는 것을 의미합니다. 이러한 많은 변경은 구조체의 크기를 변경하므로 잡히지는 않지만 모두가 아닙니다. 나는 이것에 대한 해결책이 확실하지 않다.

또는 공용 라이브러리가 포함되지 않은 특수 공개 헤더가있는 경우 (지원하는 컴파일러에 대한 테스트를 거쳐야 함) 아마도 하나의 유형으로 공용 프로토 타입을 작성할 수 있습니다 다른 하나는 내부의 것들입니다. 라이브러리가 public-facing Thing 구조체를 어떻게 든 보이기 때문에 헤더의 크기를 검사 할 수 있도록 헤더를 구성하는 것이 좋습니다.

+2

정렬 고려 사항 때문에 접근법이 잘못되었습니다. 불투명 한 구조체는'long opaque [MAX_SIZE/sizeof (long)];와 같거나 더 나은 아직 정렬을 위해 원하는 크기와 모든 "큰"유형의'char' 배열을 포함하는 공용체 일 필요가 있습니다. –

+0

솔루션이 제대로 업데이트되었습니다 ... –

+0

@R 해당 정렬 문제에 대한 질문/답변을 게시했습니다 : http://stackoverflow.com/questions/17619015/why-you-should-not-hide-structure-implementation - 그런데 – ydroneaud

0

간단합니다. privateTypes.h 헤더 파일에 구조체를 넣기 만하면됩니다. 더 이상 불투명하지는 않지만 여전히 개인 파일 안에 있기 때문에 프로그래머에게는 비공개입니다.여기

예 : Hiding members in a C struct

+0

개인적인 캡슐화의 주된 이유는 의도적으로 나쁜 일을하는 프로그래머에 대한 걱정이 아니기 때문에 프로그래머가 구조체 선언을 전역 적으로 볼 수 있다면 우연히 나쁜 일을하기 때문입니다. 이는 특히 'myfoo'를 입력 할 수있는 IDE 코드 완성시에 특히 그렇습니다. 그런 다음 IDE는 여러분이 선택할 수있는 대안을 제공하게되어 기쁩니다. – Lundin

+0

@Lundin 이것은 "TDD for Embedded C"와 같은 책에서 참고할 수있는 아이디어입니다. 나는 당신이 언급 한 단점에 동의하며, 진정한 비공개가 소프트웨어 설계 방식을 malloc의 채택과 같은 런타임 수정에서보다 어렵거나 영향을 미칠 것으로 믿습니다. –

+0

Clifford에 의해 게시 된 것과 같은이 스레드의 많은 해답은 단순한 전용 메모리 풀을 구현하여 은폐 형을 유지하는 것이 매우 간단하다는 것을 보여줍니다. 이는 임베디드 시스템에 이상적입니다. 그리고 글쎄, 나는 그 책을 한 순간에 간략하게 읽었으며별로 인상적이지는 않았다. 표준적인 참고서는 아니었다. – Lundin

1

난 불투명 데이터 구조의 헤더, 동작부터 다시 수행 될 필요가있는 모든 다양한 데이터를 보유하는 데이터 구조를 구현하는 유사한 문제에 직면 작동.

다시 초기화하면 메모리 누수가 발생할 수 있으므로 데이터 구조 구현이 실제로 할당 된 메모리 힙을 덮어 쓰지 않도록하고 싶습니다. 구현 파일에서

/** 
* In order to allow the client to place the data structure header on the 
* stack we need data structure header size. [1/4] 
**/ 
#define CT_HEADER_SIZE ((sizeof(void*) * 2)   \ 
         + (sizeof(int) * 2)    \ 
         + (sizeof(unsigned long) * 1) \ 
         ) 

/** 
* After the size has been produced, a type which is a size *alias* of the 
* header can be created. [2/4] 
**/   
struct header { char h_sz[CT_HEADER_SIZE]; }; 
typedef struct header data_structure_header; 

/* In all the public interfaces the size alias is used. [3/4] */ 
bool ds_init_new(data_structure_header *ds /* , ...*/); 

: 내가 무슨 짓을

는 다음과

struct imp_header { 
    void *ptr1, 
     *ptr2; 
    int i, 
     max; 
    unsigned long total; 
}; 

/* implementation proper */ 
static bool imp_init_new(struct imp_header *head /* , ...*/) 
{ 
    return false; 
} 

/* public interface */ 
bool ds_init_new(data_structure_header *ds /* , ...*/) 
{ 
    int i; 

    /* only accept a zero init'ed header */ 
    for(i = 0; i < CT_HEADER_SIZE; ++i) { 
     if(ds->h_sz[i] != 0) { 
      return false; 
     } 
    } 

    /* just in case we forgot something */ 
    assert(sizeof(data_structure_header) == sizeof(struct imp_header)); 

    /* Explicit conversion is used from the public interface to the 
    * implementation proper. [4/4] 
    */ 
    return imp_init_new((struct imp_header *)ds /* , ...*/); 
} 

클라이언트 측 :

int foo() 
{ 
    data_structure_header ds = { 0 }; 

    ds_init_new(&ds /*, ...*/); 
} 
+0

+1 : 그러나 구조체에서 패딩이 발생할 수 있으므로'CT_HEADER_SIZE'는'sizeof (struct imp_header)'보다 작을 수 있습니다. 그리고 저에게는 CT_HEADER_SIZE를 위해 많은 노력이 필요합니다. – jeb

+1

'struct header'는 정적으로 할당되는 경우 올바르게 정렬되지 않을 수 있습니다 :'struct imp_header'와 같은 정렬 요구 사항을 가지고 있지 않습니다. http://stackoverflow.com/a/17619016/611560을 참조하십시오. – ydroneaud

0

이 오래된 질문이지만, 그것은 또한 이후 나는 여기에 가능한 대답을 제공하고 싶었다.

// file.h 
typedef struct { size_t space[3]; } publicType; 
int doSomething(publicType* object); 

// file.c 
typedef struct { unsigned var1; int var2; size_t var3; } privateType; 

int doSomething(publicType* object) 
{ 
    privateType* obPtr = (privateType*) object; 
    (...) 
} 

장점 : publicType가 스택에 할당 할 수 있습니다 그래서 여기

은 예입니다.

적절한 정렬을 위해 올바른 기본 유형을 선택해야합니다 (즉, char을 사용하지 않음). 또한 sizeof(publicType) >= sizeof(privateType)에 유의하십시오. 이 조건이 항상 확인되도록 정적 어설 션을 제안합니다. 마지막으로, 나중에 구조가 진화한다고 생각되면 ABI를 깨지 않고 향후 확장을위한 공간을 확보하기 위해 공개 유형을 조금 더 넓게 만드는 것을 주저하지 마십시오.

단점 : 개인 유형 대중 주조는 strict aliasing warnings을 트리거 할 수 있습니다.

나중에이 방법은 BSD 소켓 내에서 struct sockaddr과 유사한 점을 발견했습니다. 이는 기본적으로 엄격한 별칭 경고와 동일한 문제를 해결합니다.