C의 API 디자인 함정


10

C API (표준 라이브러리, 타사 라이브러리 및 프로젝트 내부의 헤더 포함)에서 문제를 일으키는 몇 가지 결함은 무엇입니까? 목표는 C에서 API 디자인의 함정을 식별하여 새로운 C 라이브러리를 작성하는 사람들이 과거의 실수로부터 배울 수 있도록하는 것입니다.

결함이 왜 나쁜지 설명하고 (예를 들어서 바람직 함) 개선을 제안하려고합니다. 귀하의 솔루션이 실제 환경에서는 실용적이지 않을 수도 있지만 (해결하기에는 너무 늦습니다 strncpy), 미래의 도서관 작가에게 머리를 기울여야합니다.

이 질문의 초점은 C API이지만 다른 언어로 사용하는 데 영향을주는 문제는 환영합니다.

답변 당 하나의 결함을 제시하여 민주주의가 답변을 정렬 할 수 있습니다.


3
Joey,이 질문은 사람들이 싫어하는 것들의 목록을 만들도록 요구함으로써 건설적이지 않다는 것입니다. 답 이 그들이 지적하는 관행이 나쁜지를 설명하고 개선 방법에 대한 자세한 정보를 제공 한다면 질문이 유용 할 가능성 이 있습니다. 이를 위해 질문에서 예제를 자체 답변으로 옮기고 왜 그것이 문제인지 / malloc'd 문자열이 어떻게 고칠 지 설명하십시오. 첫 번째 대답으로 좋은 모범을 보이는 것이이 질문이 번성하는 데 실제로 도움이 될 수 있다고 생각합니다. 감사!
Adam Lear

1
@Anna Lear : 내 질문에 문제가있는 이유 를 알려 주셔서 감사합니다 . 나는 예를 요구하고 대안을 제안함으로써 그것을 건설적으로 유지하려고 노력했다. 내가 생각했던 것을 나타 내기 위해 실제로 몇 가지 예가 필요하다고 생각합니다.
Joey Adams

@Joey Adams 이런 식으로보세요. C API 문제를 일반적인 방식으로 "자동으로"해결해야하는 질문을합니다. StackOverflow와 같은 사이트는 프로그래밍과 관련된 일반적인 문제를 쉽게 찾아서 답변 할 수 있도록 설계되었습니다. StackOverflow는 자연스럽게 귀하의 질문에 대한 답변 목록을 제공하지만 더 체계적으로 쉽게 검색 할 수있는 방법으로 나타납니다.
앤드류 T 피넬

나는 내 자신의 질문을 끝내기로 투표했다. 저의 목표는 새로운 C 라이브러리에 대한 체크리스트 역할을 할 수있는 답변을 모으는 것이 었습니다. 지금까지 세 가지 답변은 모두 "일관되지 않음", "비논리적"또는 "혼란"과 같은 단어를 사용합니다. API가 이러한 답변을 위반하는지 여부를 객관적으로 결정할 수 없습니다.
Joey Adams

답변:


5

일치하지 않거나 비논리적 인 반환 값이있는 함수 두 가지 좋은 예 :

1) HANDLE을 반환하는 일부 Windows 함수는 오류 (CreateThread)에 NULL / 0을 사용하고 일부는 오류 (CreateFile)에 INVALID_HANDLE_VALUE / -1을 사용합니다.

2) POSIX 'time'함수는 오류시 '(time_t) -1'을 반환합니다. 'time_t'는 부호있는 유형이거나 부호없는 유형일 수 있으므로 실제로는 비논리적입니다.


2
실제로 time_t는 (보통) 서명됩니다. 그러나 1969 년 12 월 31 일을 "유효하지 않은"것으로 부르는 것은 비논리적입니다. 나는 진지, 솔루션이 같이 에러 코드를 반환하고, 포인터를 통해 결과를 전달하는 것입니다 :-) 60 년대 거친했다 추측 : int time(time_t *out);BOOL CreateFile(LPCTSTR lpFileName, ..., HANDLE *out);.
Joey Adams

바로 그거죠. time_t에 부호가 없으면 이상하고 time_t에 부호가 있으면 유효한 바다의 한가운데서 번은 무효가됩니다.
David Schwartz

4

비 설명 적이거나 혼동되는 이름을 가진 기능 또는 매개 변수. 예를 들면 다음과 같습니다.

1) Windows API에서 CreateFile은 실제로 파일을 만들지 않고 파일 핸들을 만듭니다. 매개 변수를 통해 요청하면 '열기'와 마찬가지로 파일을 만들 수 있습니다. 이 매개 변수에는 이름이 의미를 암시하지 않는 'CREATE_ALWAYS'및 'CREATE_NEW'라는 값이 있습니다. ( 'CREATE_ALWAYS'는 파일이 존재하는 경우 실패하거나 그 위에 새 파일을 작성하는 것을 의미합니까? 'CREATE_NEW'는 항상 새 파일을 작성하고 파일이 이미 존재하는 경우 실패하는 것을 의미합니까? 그 위에 파일을?)

2) POSIX pthreads API의 pthread_cond_wait는 이름에도 불구하고 무조건 대기입니다.


1
COND 의는 pthread_cond_wait"조건부 대기"를 의미하지 않는다. 조건 변수를 기다리고 있다는 사실을 나타냅니다 .
Jonathon Reinhart

오른쪽, 그것은 무조건 기다리는 건 에 대한 조건은.
David Schwartz

3

유형 삭제 된 핸들로 인터페이스를 통해 전달되는 불투명 한 유형. 물론 문제는 컴파일러가 사용자 코드에서 올바른 인수 유형을 확인할 수 없다는 것입니다.

이것은 다음을 포함하지만 이에 국한되지 않는 다양한 형태와 풍미로 제공됩니다.

  • void* 남용

  • 사용 int리소스 핸들로 (예 : CDI 라이브러리)

  • 문자열 형식의 인수

보다 고유 한 유형 (= 완전히 교환하여 사용할 수 없음)이 동일한 유형의 삭제 된 유형에 맵핑 될수록 나빠집니다. 물론, 해결책은 단순히 (C 예)의 라인을 따라 타입 안전 불투명 포인터를 제공하는 것입니다.

typedef struct Foo Foo;
typedef struct Bar Bar;

Foo* createFoo();
Bar* createBar();

int doSomething(Foo* foo);
void somethingElse(Foo* foo, Bar* bar);

void destroyFoo(Foo* foo);
void destroyBar(Bar* bar);

2

일관성이없고 종종 성가신 문자열 반환 규칙이있는 함수.

예를 들어, getcwd 는 사용자 제공 버퍼 및 크기를 요청합니다. 이것은 응용 프로그램이 현재 디렉토리 길이에 임의의 제한을 설정하거나 CCAN에서 다음과 같은 작업을 수행해야 함을 의미합니다 .

 /* *This* is why people hate C. */
len = 32;
cwd = talloc_array(ctx, char, len);
while (!getcwd(cwd, len)) {
    if (errno != ERANGE) {
        talloc_free(cwd);
        return NULL;
    }
    cwd = talloc_realloc(ctx, cwd, char, len *= 2);
}

내 해결책 : malloced 문자열을 반환하십시오 . 간단하고 강력하며 덜 효율적입니다. 임베디드 플랫폼과 구형 시스템을 제외하면 malloc실제로는 매우 빠릅니다.


4
나는 이것을 나쁜 습관이라고 부르지 않을 것이고, 나는 이것을 좋은 습관이라고 부르겠다. 1) 프로그래머가 놀라지 않을 정도로 매우 일반적입니다. 2) 메모리 누수 버그의 많은 가능성을 배제하는 호출자에게 할당을 남겨 둡니다. 3) 정적으로 할당 된 버퍼와 호환됩니다. 4) 함수 구현을 더 깨끗하게 만듭니다. 일부 수학 공식을 계산하는 함수는 동적 메모리 할당과 같이 전혀 관련이없는 것에 관심을 가져서는 안됩니다. main은 깨끗해 지지만 기능은 더 복잡해집니다. 5) malloc은 많은 시스템에서 허용되지 않습니다.

@Lundin : 문제는 프로그래머가 불필요한 하드 코딩 된 한계를 생성하게 만들고 실제로 시도하지 않아야한다는 것입니다 (위의 예 참조). 것들에 대한 그것의 벌금 좋아하는 snprintf(buf, 32, "%d", n)(하지 않는 한, 확실히 30이 아닌 출력 길이가 예측이고, int이다 정말 시스템에 큰). 실제로 malloc은 많은 시스템에서 사용할 수 없지만 데스크탑 및 서버 환경에서는 유효하며 실제로 잘 작동합니다.
Joey Adams

그러나 문제는 예제의 함수가 하드 코딩 된 제한을 설정하지 않는다는 것입니다. 이와 같은 코드는 일반적인 관행이 아닙니다. 여기서 main은 함수가 알고 있어야하는 버퍼 길이에 대해 알고 있습니다. 그것은 모두 프로그램 디자인이 좋지 않다는 것을 암시합니다. Main은 getcwd 함수의 기능을 알지 못하는 것 같으므로 "브 루트 포스"할당을 사용하여 알아냅니다. getcwd가 상주하는 모듈과 호출자 사이의 인터페이스가 혼잡합니다. 그렇다고해서 함수 호출 방법이 나쁘다는 것은 아닙니다. 반면에 경험에 비추어 볼 때 이미 언급 한 이유가 좋습니다.

1

복합 데이터 유형을 값으로 가져 오거나 리턴하거나 콜백을 사용하는 함수.

해당 유형이 공용체이거나 비트 필드를 포함하는 경우 더 나쁩니다.

C 호출자의 관점에서 보면, 이것들은 실제로는 괜찮지 만, 필요한 경우가 아니면 C 또는 C ++로 작성하지 않으므로 일반적으로 FFI를 통해 호출합니다. 대부분의 FFI는 공용체 또는 비트 필드를 지원하지 않으며 일부 (예 : Haskell 및 MLton)는 값으로 전달 된 구조체를 지원할 수 없습니다. 값별 구조체를 처리 할 수있는 사용자의 경우 최소한 공통 Lisp 및 LuaJIT가 느린 경로로 강제 실행됩니다. Lisp의 Common Foreign Function Interface는 libffi를 통해 느린 호출을 수행해야하며 LuaJIT는 호출이 포함 된 코드 경로를 JIT 컴파일하지 않습니다. 호스트로 다시 호출 할 수있는 함수도 LuaJIT, Java 및 Haskell에서 느린 경로를 트리거하며 LuaJIT는 이러한 호출을 컴파일 할 수 없습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.