programing

C API 설계:누가 할당해야 합니까?

goodcopy 2022. 7. 5. 23:10
반응형

C API 설계:누가 할당해야 합니까?

C API에서 메모리를 할당하는 적절한 방법 또는 권장되는 방법은 무엇입니까?

처음에는 두 가지 옵션을 볼 수 있습니다.

1) 발신자가 모든 (외부) 메모리 처리를 하도록 합니다.

myStruct *s = malloc(sizeof(s));
myStruct_init(s);

myStruct_foo(s);

myStruct_destroy(s);
free(s);

_init ★★★★★★★★★★★★★★★★★」_destroy이 기능은 내부에 메모리를 할당할 수 있기 때문에 필요하며, 다른 곳에서 처리해야 합니다.

이는 더 길다는 단점이 있지만 경우에 따라서는 malloc를 제거할 수도 있습니다(예를 들어 스택 할당 구조를 전달할 수 있습니다).

int bar() {
    myStruct s;
    myStruct_init(&s);

    myStruct_foo(&s);

    myStruct_destroy(&s);
}

또, 발신자가 구조물의 크기를 알 필요가 있습니다.

숨기다 2) 숨기다malloc in 에 s있 s_init ★★★★★★★★★★★★★★★★★」free in 에 s있 s_destroy.

장점: 함수가 호출되기 때문에 코드가 짧아집니다.완전히 불투명한 구조입니다.

단점:다른 방식으로 할당된 구조물을 전달할 수 없습니다.

myStruct *s = myStruct_init();

myStruct_foo(s);

myStruct_destroy(foo);

저는 현재 첫 번째 케이스에 기대고 있습니다만, C API 디자인은 잘 모릅니다.

#2의 또 다른 단점은 발신자가 할당 방법을 제어할 수 없다는 것입니다.이것은 클라이언트가 독자적인 할당/해제 함수를 등록하기 위한 API를 제공함으로써 해결할 수 있지만(SDL과 마찬가지로) 그마저도 충분히 세분화되지 않을 수 있습니다.

#1의 단점은 출력 버퍼가 고정 크기(예: 문자열)가 아닐 경우 제대로 작동하지 않는다는 것입니다.그 후, 발신자가 버퍼를 할당할 수 있도록, 우선 버퍼의 길이를 취득하는 다른 함수를 제공할 필요가 있습니다.최악의 경우 이를 효율적으로 수행하는 것은 불가능합니다(즉, 한 번에 컴퓨팅과 복사를 수행하는 것보다 별도의 경로로 컴퓨팅하는 시간이 너무 많이 소요됩니다).

#2의 장점은 데이터 유형을 불투명 포인터로 엄격하게 노출할 수 있다는 것입니다(즉, 구조를 선언하지만 정의하지 않고 포인터를 일관되게 사용).그런 다음 클라이언트는 바이너리 수준에서 호환성을 유지하면서 라이브러리의 향후 버전에 적합하도록 구조의 정의를 변경할 수 있습니다.에서는 클라이언트에 를 들어, #1의 버전을 지정해야 합니다.cbSizeWin32 API의 필드)를 사용하여 라이브러리가 진화해도 바이너리 호환성을 유지할 수 있도록 구조의 이전 버전과 최신 버전을 모두 처리할 수 있는 코드를 수동으로 작성합니다.

일반적으로 당신의 구조가 향후 라이브러리의 마이너 리비전에도 변하지 않는 투명한 데이터라면, 저는 #1로 하겠습니다.다소 복잡한 데이터 객체이며 향후 개발을 위해 완전한 캡슐화를 통해 오류를 방지하려면 #2를 선택하십시오.

방법 2는 매번.

왜냐고요? 1번 방법에서는 구현 세부사항을 발신자에게 누설해야 하기 때문입니다.발신자는 적어도 구조물이 얼마나 큰지 알아야 합니다.개체를 사용하는 코드를 다시 컴파일하지 않고 개체의 내부 구현을 변경할 수 없습니다.

첫 번째 메서드의 문제는 발신자가 더 길다는 것이 아니라 api가 수신한 메모리가 어떻게 할당되었는지 모르기 때문에 사용하는 메모리의 양을 정확하게 확장할 수 있다는 것입니다.발신자가 필요한 메모리의 양을 미리 알고 있는 것은 아닙니다(벡터를 실장하려고 했을 경우).

당신이 언급하지 않은 또 다른 옵션은 대부분의 경우 오버킬이 될 수 있지만 api가 할당자로 사용하는 함수 포인터를 전달하는 것입니다.이렇게 하면 스택을 사용할 수 없지만 malloc 사용을 메모리 풀로 대체하는 등의 작업을 수행할 수 있습니다.이것에 의해, api가 할당하는 타이밍을 제어할 수 있습니다.

어떤 방식이 적절한 API 설계인지에 대해서는 C 표준 라이브러리에서 양방향으로 이루어집니다.strdup()과 stdio는 두 번째 메서드를 사용하고 sprintf와 strcat은 첫 번째 메서드를 사용합니다.개인적으로는 두 번째 방법(또는 세 번째)을 선호합니다.다만 1) 재할당이 필요없고 2) 오브젝트의 수명이 짧기 때문에 스택을 사용하는 것이 매우 편리하다고 생각합니다.

edit: 다른 옵션이 1개 있습니다.이것은 전례가 있는 나쁜 옵션입니다.strtok()이 statics를 사용하는 방법으로 실행할 수 있습니다.좋진 않아요, 그냥 완전성을 위해서 언급했을 뿐이에요.

잘 디자인된 C API의 가장 좋아하는 예는 당신이 설명한 방법 #2를 사용하는 GTK+입니다.

메서드 #1의 또 다른 장점은 오브젝트를 스택에 할당할 수 있을 뿐만 아니라 같은 인스턴스를 여러 번 재사용할 수 있다는 것입니다.일반적인 사용 사례가 아니라면 2번이 단순하다는 장점이 있을 것입니다.

물론, 그건 제 의견일 뿐입니다.

왜 둘 다 제공하지 않는 거죠? 양쪽 모두의 장점을 얻기 위해서요?

_init 및 _terminate 함수를 사용하여 메서드 #1(또는 적합하다고 생각되는 이름)을 사용합니다.

동적 할당에는 추가 _create 및 _destroy 함수를 사용합니다._init 및 _terminate는 이미 존재하므로 사실상 다음과 같이 요약됩니다.

myStruct *myStruct_create ()
{
    myStruct *s = malloc(sizeof(*s));
    if (s) 
    {
        myStruct_init(s);
    }
    return (s);
}

void myStruct_destroy (myStruct *s)
{
    myStruct_terminate(s);
    free(s);
}

, 와 _terminate 를 합니다.staticAPI를 사용합니다.특정 콜백과 같은 다른 할당이 필요한 경우, 이를 위한 다른 함수 세트(예: _create, _destroycalled)를 제공합니다.

중요한 것은 할당을 추적하는 것이지만, 어쨌든 이 작업을 수행해야 합니다.할당 해제에는 항상 사용된 할당자의 상대 부분을 사용해야 합니다.

둘 다 기능적으로 동등합니다.하지만 제 생각에는 2번 방법이 더 사용하기 쉬워요.1보다 2를 선호하는 이유는 다음과 같습니다.

  1. 아! 꼭 전화해야 요?freemyStruct_Destroy

  2. 의 상세 내용을 숨깁니다.myStruct따위는 쓰지 됩니다.을 사용하다

  3. #2에서는 '#2myStruct_init는 오브젝트의 초기 상태에 대해 걱정할 필요가 없습니다.

  4. 가 전화하는 free.

그러나 API 구현이 별도의 공유 라이브러리로 제공되는 경우 방법 #2는 필수입니다.을 의 합니다.malloc/new ★★★★★★★★★★★★★★★★★」free/delete컴파일러 버전 전체에서 메모리 할당 및 디프로세이션은 사용자만 알고 있어야 합니다.C++로 하다

어느 쪽이든 좋습니다.C의 대부분은 임베디드 시스템용이고 메모리는 스택상의 작은 변수이거나 정적으로 할당되어 있기 때문에 첫 번째 방법을 사용하는 경향이 있습니다.이렇게 하면 메모리가 부족해지는 일은 없습니다.처음에는 충분한 메모리가 있거나 처음부터 실패하거나 둘 중 하나입니다.2K의 RAM을 탑재하고 있는 것은 매우 좋은 일입니다.그래서 모든 라이브러리는 메모리가 할당되어 있는 것으로 상정되는 #1과 같습니다.

하지만 이것은 C 개발의 엣지 케이스입니다.

그래도 1위 할 것 같아요.이름에 대해 init과 finalize/dispose(파기하지 않고)를 사용하는 경우가 있습니다.

그것은 반사적인 요소를 줄 수 있다.

케이스 #1은 C++의 메모리 할당 방식을 모방하여 다음과 같은 이점을 제공합니다.

  • 스택(또는 스태틱 어레이 등)에서 malloc를 대체하는 자체 구조 할당기를 쓰기 위한 임시 할당이 용이합니다.
  • 초기화 시 문제가 발생할 경우 메모리 빈 용량이 용이함

case #2는 사용된 구조에 대한 더 많은 정보를 숨기고 불투명 구조에도 사용할 수 있습니다. 일반적으로 사용자가 보는 구조가 lib에 의해 내부적으로 사용되는 것과 완전히 동일하지 않은 경우(구조 끝에는 더 많은 필드가 숨겨져 있을 수 있음).

case #1과 case #2 사이의 혼합 API도 일반적입니다.이미 초기화된 구조체에 포인터를 전달하기 위해 사용되는 필드가 있습니다.NULL일 경우 포인터가 할당됩니다(그리고 포인터는 항상 반환됩니다).이러한 API에서는 init이 할당을 실행했더라도 일반적으로 무료는 발신자의 책임입니다.

대부분의 경우, 저는 1번 케이스로 하겠습니다.

두 가지 모두 허용 가능합니다. 말씀하신 바와 같이 둘 사이에는 트레이드오프가 있습니다.

Dean Harding이 말했듯이 GTK+는 두 번째 방법을 사용하고 OpenSSL은 첫 번째 방법을 사용하는 예입니다.

간단한 내선번호 하나, 즉 당신의 내선번호가 있는 (1)로 하겠습니다._init함수는 항상 포인터를 개체로 반환합니다.포인터의 초기화는 다음과 같습니다.

myStruct *s = myStruct_init(malloc(sizeof(myStruct)));

보시다시피 오른쪽에는 유형만 참조되고 변수에 대한 참조는 더 이상 표시되지 않습니다.그런 다음 간단한 매크로를 통해 적어도 부분적으로 다음과 같은 정보를 얻을 수 있습니다.

#define NEW(T) (T ## _init(malloc(sizeof(T))))

포인터의 초기화는 다음과 같습니다.

myStruct *s = NEW(myStruct);

방법 #2 참조

myStruct *s = myStruct_init();

myStruct_foo(s);

myStruct_destroy(s);

이제 확인해보겠습니다.myStruct_init()여러 가지 이유로 에러 코드를 반환해야 합니다.그러면 이쪽입니다.

myStruct *s;
int ret = myStruct_init(&s);  // int myStruct_init(myStruct **s);

myStruct_foo(s);

myStruct_destroy(s);

언급URL : https://stackoverflow.com/questions/3296302/c-api-design-who-should-allocate

반응형