불투명 데이터 유형의 정적 할당
임베디드 시스템의 프로그래밍에서는 malloc()가 절대 허용되지 않는 경우가 많습니다.대부분의 경우 이 문제에 대처할 수 있지만, 한 가지 귀찮은 점이 있습니다. 즉, 데이터 은닉을 가능하게 하기 위해 이른바 'Opaque type'을 사용할 수 없다는 것입니다.보통은 이렇게 해요.
// 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에 데이터를 숨길 수 있는 적절한 방법을 가진 사람이 있는지 알고 싶습니다(물론 모듈에서 정적 글로벌을 사용하지 않는 경우 여러 인스턴스를 생성할 수 있어야 합니다).
유감스럽게도 이 문제에 대처하는 전형적인 방법은 단순히 프로그래머가 오브젝트를 불투명하게 취급하는 것이라고 생각합니다.전체 구조 구현은 헤더에 있으며 이용 가능합니다.개체에 정의된 API를 통해서만 내부 구조를 직접 사용하지 않는 것은 프로그래머의 책임입니다.
이것으로 충분치 않은 경우는, 다음의 몇개의 옵션을 사용할 수 있습니다.
- 더 나은 C를 C++로 합니다.
private
. - 헤더에 대해 일종의 사전 테스트를 실행하여 구조 내부가 사용 불가능한 이름으로 선언되도록 합니다.원래 헤더는 좋은 이름으로 구조를 관리하는 API 구현에 사용할 수 있습니다.나는 이 기술이 사용되는 것을 본 적이 없다 - 그것은 가능할지도 모르는 생각일 뿐이지만, 가치가 있는 것보다 훨씬 더 골칫거리처럼 보인다.
- 스태틱하게 가 "Displayed"로 됩니다.
extern
( globals 「」, 「」) 할 수 이 실제로 오브젝트를 합니다그런 다음 개체의 전체 정의에 액세스할 수 있는 특수 모듈을 사용하여 실제로 이러한 개체를 선언합니다. 수 은 여전히 특수' 모듈만 전체 정의에 액세스할 수 있으므로 불투명 개체의 일반적인 사용은 불투명합니다.그러나 이제는 오브젝트가 글로벌하다는 사실을 악용하지 않도록 프로그래머에게 의존해야 합니다.또한 이름 충돌의 변경도 증가했기 때문에 이를 관리해야 합니다(의도하지 않고 발생할 수 있다는 점만 제외하면 큰 문제는 아닐 수 있습니다).
전반적으로 프로그래머에게 이러한 오브젝트의 사용 규칙을 따르는 것이 최선의 해결책이라고 생각합니다(C++의 서브셋을 사용하는 것도 나쁘지 않다고 생각합니다).내부 구조를 사용하지 않는 규칙을 따르는 것은 프로그래머에 따라 완벽하지는 않지만 일반적으로 사용되는 실행 가능한 솔루션입니다.
으로 1개의 .struct handle_t
오브젝트, 그리고 필요조건으로 제공합니다.이를 실현하는 방법은 여러 가지가 있지만, 간단한 예시를 다음에 제시하겠습니다.
// 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에 도달하면 '손잡이'를 할 수 있습니다.이것은 하나의 핸들을 해제하기 전에 여러 개의 핸들을 할당하는 일반적인 상황에서는 더 빠릅니다.단, 소수의 핸들에서는 이 brute-force 검색이 적절할 수 있습니다.
물론 핸들 자체가 포인터가 될 필요는 없지만 숨겨진 풀에 대한 단순한 인덱스가 될 수 있습니다.이를 통해 외부 액세스로부터 데이터를 숨기고 풀을 보호할 수 있습니다.
따라서 헤더에는 다음이 포함됩니다.
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 ;
}
반환된 핸들은 더 이상 내부 데이터에 대한 포인터가 아니며, 캐묻거나 악의적인 사용자는 핸들을 통해 해당 데이터에 액세스할 수 없습니다.
여러 스레드에 핸들이 있을 경우 몇 가지 스레드 안전 메커니즘을 추가해야 할 수 있습니다.
_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;
}
내 비트 시프트가 좀 이상해서, 내가 한 지 오래되었지만, 요점을 이해하길 바래.
한 가지 방법은 다음과 같은 것을 추가하는 것입니다.
#define MODULE_HANDLE_SIZE (4711)
module.h
머리글따라서 실제 크기와 동기화 상태를 유지해야 하는 번거로운 요구 사항이 발생하기 때문에 이 라인은 물론 빌드 프로세스에서 자동으로 생성되는 것이 가장 좋습니다.
다른 옵션은 물론 실제로 구조를 노출시키는 것이지만, 정의된 API를 통한 접근 이외의 다른 수단을 통한 접근은 불가능하다고 문서화합니다.이는 다음과 같은 방법으로 보다 명확하게 할 수 있습니다.
#include "module_private.h"
typedef struct
{
handle_private_t private;
} handle_t;
여기서 모듈 핸들의 실제 선언은 눈에 잘 띄지 않게 하기 위해 별도의 헤더로 이동되었습니다. 타입으로 .typedef
름,,, 반반반개을을다 다다다다다다
내에서 ""를 받는 handle_t *
하게 할 수 있다private
로 handle_private_t
가치관입니다. 왜냐하면 그것은 공공 구조의 첫 번째 구성원이기 때문입니다.
간단해, 구조물을 개인에 넣기만 하면 돼Types.h 헤더파일이 파일은 더 이상 불투명하지 않지만, 비공개 파일 안에 있기 때문에 프로그래머에게는 비공개 파일입니다.
예를 들어 다음과 같습니다.C 구조체의 부재 숨기기
운용에서 운용으로 이월해야 할 각종 데이터를 불투명한 데이터 구조의 헤더에 모두 저장하는 데이터 구조 구현에서도 비슷한 문제에 직면했다.
재초기화로 인해 메모리 누수가 발생할 수 있으므로 데이터 구조 구현 자체가 실제로 힙 할당 메모리의 포인트를 덮어쓰지 않도록 하고 싶었습니다.
제가 한 일은 다음과 같습니다.
/**
* 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 /*, ...*/);
}
왜 malloc을 사용할 수 없다고 하는지 좀 헷갈리네요.임베디드 시스템에서는 메모리가 한정되어 있기 때문에, 통상의 솔루션은, 독자적인 메모리 매니저를 가지는 것입니다.이 매니저는 대용량 메모리 풀을 malloc하고, 필요에 따라서 이 풀을 할당하는 것입니다.나는 이 아이디어의 다양한 구현들을 내 시대에 봐왔다.
단, 질문에 답하려면 module.c에서 고정 크기 배열을 정적으로 할당하고 "사용 중" 플래그를 추가한 후 create_handle()을 사용하여 포인터를 첫 번째 자유 요소로 되돌리면 됩니다.
이 아이디어의 연장선상에서 "핸들"은 실제 포인터가 아닌 정수 인덱스가 될 수 있으며, 사용자가 오브젝트에 대한 자신의 정의에 따라 포인터를 오용하려는 가능성을 방지할 수 있습니다.
지금까지 본 것 중 가장 암울한 해결책은 발신자가 사용할 수 있는 불투명한 구조를 제공하는 것입니다.그것은 충분히 크고, 게다가 실제 구조에서 사용되는 타입에 대해서도 언급하고 있기 때문에, 불투명한 구조가 실제 구조보다 충분히 잘 정렬되어 있는 것을 확인할 수 있습니다.
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' -- uint8_t' -- uint8_t' -- uint8_t' -- uint8_t' -- uint8_t' -- uint8_t'은 이 두 가지 유형이 이유입니다.
에서는, 「하다」가 확실히 행해지고 있는 것을 할 수 .RealThing
의 가 의 Thing
:
typedef char CheckRealThingSize[sizeof(RealThing)<=sizeof(Thing)?1:-1];
그런 다음 라이브러리의 각 함수는 인수를 사용할 때 해당 인수를 캐스팅합니다.
void InitThing(Thing *thing) {
RealThing *t=(RealThing *)thing;
/* stuff with *t */
}
이렇게 하면 발신자는 스택 상에 적절한 크기의 오브젝트를 작성할 수 있으며, 이 오브젝트에 대해 함수를 호출할 수 있습니다.구조는 여전히 불투명하며, 불투명한 버전이 충분히 큰지 확인할 수 있습니다.
잠재적인 문제 중 하나는 필드를 실제 구조에 삽입할 수 있다는 것입니다. 즉, 불투명 구조에는 들어가지 않는 정렬이 필요하며, 이것이 반드시 크기 검사를 방해하지는 않습니다.그런 많은 변화가 구조물의 크기를 바꿀 것이고, 그래서 그들은 잡힐 것이다, 하지만 전부는 아니다.나는 이것에 대한 어떤 해결책도 확신할 수 없다.
또는 라이브러리가 자신을 포함하지 않는 특별한 공개용 헤더가 있는 경우 (지원하는 컴파일러에 대한 테스트에 따라) 공개용 프로토타입과 내부용 프로토타입을 각각 다른 유형으로 작성할 수 있습니다.을 볼 수 하는 것이 .Thing
크기를 확인할 수 있도록 구조화했습니다.
이것은 오래된 질문이지만, 그것 또한 나를 괴롭히고 있기 때문에, 나는 여기에서 가능한 대답을 하고 싶었다(내가 사용하고 있는 것).
예를 들어 다음과 같습니다.
// 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를 깨지 않고 미래의 확장을 위한 여지를 확보하세요.
단점 : 퍼블릭유형에서 프라이빗유형으로 캐스팅되면 엄밀한 에일리어싱 경고가 트리거될 수 있습니다.
나중에 알게 된 사실은 이 방법이struct sockaddr
BSD 소켓 내에서는 엄밀한 에일리어싱 경고와 같은 문제가 발생합니다.
여기서의 코멘트에 관한 오래된 논의를 확대하려면 컨스트럭터 호출의 일부로 할당자 함수를 제공하여 이를 수행할 수 있습니다.
한 타입이 있는
typedef struct opaque opaque;
(그래서)함수의 합니다.
typedef void* alloc_t (size_t bytes);
, 는 우, 는, 는, 는, 는, 는, as, as, as, ., ., as, ., ., ., ., ., ., ., ., ., ., asmalloc
/alloca
를 참조해 주세요.컨스트럭터의 실장은 다음과 같습니다.
struct opaque { int foo; // some private member }; opaque* opaque_construct (alloc_t* alloc, int some_value) { opaque* obj = alloc(sizeof *obj); if(obj == NULL) { return NULL; } // initialize members obj->foo = some_value; return obj; }
즉, opauqe 객체의 크기를 생성자 내부에서 알 수 있습니다.
임베디드 시스템에서와 같은 정적 스토리지 할당의 경우 다음과 같은 단순한 정적 메모리 풀 클래스를 만들 수 있습니다.
#define MAX_SIZE 100 static uint8_t mempool [MAX_SIZE]; static size_t mempool_size=0; void* static_alloc (size_t size) { uint8_t* result; if(mempool_size + size > MAX_SIZE) { return NULL; } result = &mempool[mempool_size]; mempool_size += size; return result; }
은, ( 「 」 , 「 」 )에 할당되어 있습니다.
.bss
또는 커스텀 섹션(필요한 경우)을 참조해 주세요.이것에 의해, 발신자는 각 오브젝트의 할당 방법을 결정할 수 있게 되어, 예를 들면 자원 제약 마이크로 컨트롤러가 같은 메모리 풀을 공유할 수 있게 됩니다.사용방법:
opaque* obj1 = opaque_construct(malloc, 123); opaque* obj2 = opaque_construct(static_alloc, 123); opaque* obj3 = opaque_construct(alloca, 123); // if supported
이것은 메모리 절약에 도움이 됩니다.마이크로컨트롤러 어플리케이션에 복수의 드라이버가 있어 각각 HAL 뒤에 숨는 것이 적절한 경우 드라이버 실장자가 각 불투명한 유형의 인스턴스 수를 추측할 필요 없이 동일한 메모리 풀을 공유할 수 있습니다.
예를 들어 UART, SPI 및 CAN에 대한 하드웨어 주변기기용 범용 HAL이 있다고 가정해 보겠습니다.드라이버를 실장할 때마다 독자적인 메모리 풀을 제공하는 것이 아니라, 일원화된 섹션을 공유할 수 있습니다. 보통 , 가 있다, 상수가 있다, 상수가 있다, 상수가 있다, 이런 으로 풀어요.UART_MEMPOOL_SIZE 5
exposed in exposed in 。uart.h
사용자가 필요한 UART 객체의 수(일부 MCU 상의 현재 UART 하드웨어 주변기기 수, 일부 CAN 구현에 필요한 CAN 버스 메시지 객체의 수 등) 후에 변경할 수 있도록 합니다.「」를 사용합니다.#define
일반적으로 애플리케이션 프로그래머가 제공된 표준화된 HAL 헤더를 가지고 장난치는 것을 원치 않기 때문에 상수는 유감스러운 설계입니다.
언급URL : https://stackoverflow.com/questions/4440476/static-allocation-of-opaque-data-types
'programing' 카테고리의 다른 글
파일 내용을 C 문자열로 읽는 방법은 무엇입니까? (0) | 2022.07.30 |
---|---|
스택 할당, 패딩 및 정렬 (0) | 2022.07.30 |
Vuejs - 템플릿의 헤더 섹션을 부모로 이동 (0) | 2022.07.30 |
스트링 리터럴에 헥스 이스케이프를 적절히 추가하는 방법은? (0) | 2022.07.30 |
오늘 자정과 내일 자정이라는 Java Date 개체를 만들려면 어떻게 해야 합니까? (0) | 2022.07.30 |