객체 지향 C를 작성하는 것이 좋지 않습니까? [닫은]


14

나는 항상 객체 지향적 인 코드를 C로 작성하는 것처럼 보이므로 소스 파일이나 구조체를 만들 것이라고 말한 다음이 구조체에 대한 포인터를이 구조체가 소유 한 함수 (메서드)에 전달하십시오.

struct foo {
    int x;
};

struct foo* createFoo(); // mallocs foo

void destroyFoo(struct foo* foo); // frees foo and its things

이것은 나쁜 습관입니까? C를 "적절한 방법"으로 작성하는 방법을 배우려면 어떻게해야합니까?


10
리눅스 (커널)의 많은 부분이 이런 식으로 쓰여졌으며, 사실상 가상 메소드 디스패치 (virtual method dispatch)와 같은 훨씬 더 많은 OO와 같은 개념을 모방합니다. 나는 그것이 꽤 적절하다고 생각합니다.
Kilian Foth

13
" [T] 그는 진짜 프로그래머는 어떤 언어로 FORTRAN 프로그램을 작성할 수 결정했다. "- 에드 포스트 1983
로스 패터슨

4
C ++로 전환하고 싶지 않은 이유가 있습니까? 마음에 들지 않는 부분을 사용할 필요는 없습니다.
svick

5
이것은 " '객체 지향'이란 무엇인가?" 이 객체 지향을 호출하지 않습니다. 나는 절차 적이라고 말할 것입니다. (당신은 상속, 다형성, 상태를 숨길 수있는 캡슐화 / 기능이 없으며 아마도 내 머리 위로 떨어지지 않는 다른 OO의 특징이 누락되었을 것입니다.) 좋은지 나쁜지 여부는 이러한 의미에 의존하지 않습니다. , 그러나.
jpmc26

3
@ jpmc26 : 언어 규범 론자라면 Alan Kay의 말을 들어야합니다. Alan Kay라는 용어를 발명했고 그 의미를 말하고 OOP가 메시징에 관한 것 입니다. 언어 기술자 인 경우 소프트웨어 개발 커뮤니티에서 용어의 사용법을 조사합니다. Cook은 정확히 그렇게했으며 OO라고 주장하거나 OO로 간주되는 언어의 기능을 분석 했으며 공통점이 한 가지 있음을 발견했습니다. 메시징 .
Jörg W Mittag

답변:


24

아니, 이건 그것도 하나에도 같은 규칙을 사용할 수 있지만, 그렇게하도록 권장 나쁜 방법이 아닙니다 struct foo *foo_new();void foo_free(struct foo *foo);

물론, 의견에서 알 수 있듯이 적절한 경우에만이 작업을 수행하십시오. 에 생성자를 사용하는 것은 의미가 없습니다 int.

접두사 foo_는 다른 라이브러리의 이름이 충돌하는 것을 막기 때문에 많은 라이브러리가 따르는 규칙입니다. 다른 함수는 종종 사용하는 규칙이 있습니다 foo_<function>(struct foo *foo, <parameters>);. 이를 통해 struct foo불투명 한 유형이 될 수 있습니다 .

규칙 에 대한 libcurl 문서 , 특히 "subnamespaces"를 살펴보면 curl_multi_*첫 번째 매개 변수가에 의해 리턴 될 때 함수 호출이 첫눈에 틀리게 보일 수 curl_easy_init()있습니다.

더 일반적인 접근 방식 이 있습니다. ANSI-C를 사용한 객체 지향 프로그래밍을 참조하십시오.


11
항상 "적절한 곳"에주의하십시오. OOP는 은총 탄이 아닙니다.
중복 제거기

C에는 이러한 함수를 선언 할 수있는 네임 스페이스가 없습니까? 과 (와) 유사 std::string하지 foo::create않습니까? 저는 C를 사용하지 않습니다. 아마도 C ++에만 있습니까?
Chris Cirefice

@ChrisCirefice C에는 네임 스페이스가 없으므로 많은 라이브러리 작성자가 함수에 접두사를 사용합니다.
Residuum

2

나쁘지 않고 훌륭합니다. 객체 지향 프로그래밍은 좋은 것입니다 (휴식을 취하지 않으면 너무 많은 것을 가질 있습니다). C는 OOP에 가장 적합한 언어는 아니지만 그것을 최대한 활용하는 것을 막을 수는 없습니다.


4
나는 동의 할 수 있지만, 당신의 의견은 정말 지원해야하지 일부 동화.
중복 제거기

1

나쁘지 않습니다. 많은 버그 (메모리 누수, 초기화되지 않은 변수 사용, 무료 사용 후 사용 등)로 인해 보안 문제가 발생할 수있는 RAII 사용을 승인합니다 .

따라서 GCC 또는 Clang (MS 컴파일러가 아닌)으로 만 코드를 컴파일 cleanup하려면 객체를 올바르게 파괴 하는 속성 을 사용할 수 있습니다. 객체를 그렇게 선언하면 :

my_str __attribute__((cleanup(my_str_destructor))) ptr;

그런 다음 my_str_destructor(ptr)ptr이 범위를 벗어날 때 실행됩니다. 함수 인수 와 함께 사용할 수 없다는 점을 명심하십시오 .

또한 네임 스페이스가 없으므로 my_str_메소드 이름 에 사용해야 하며 C다른 함수 이름과 쉽게 충돌 할 수 있습니다.


2
Afaik, RAII는 C ++에서 객체에 대한 소멸자의 암시 적 호출을 사용하여 정리를 보장하고 명시적인 리소스 릴리스 호출을 추가 할 필요가 없습니다. 따라서 내가 실수하지 않으면 RAII와 C는 상호 배타적입니다.
cmaster-복원 monica

@cmaster 만약 당신이 #define당신의 typename을 사용 __attribute__((cleanup(my_str_destructor)))한다면 당신은 그것을 전체 #define범위 에서 암시 적으로 얻을 것입니다 (모든 변수 선언에 추가 될 것입니다).
Marqin

a) gcc를 사용하고 b) 함수 로컬 변수에서만 유형을 사용하는 경우 c) 알몸 버전에서만 유형을 사용하는 경우 ( #define'd 유형 또는 그 배열에 대한 포인터 없음 ) 작동합니다. 한마디로 : 그것은 표준 C가 아니며, 사용시 많은 융통성으로 지불합니다.
cmaster-monica reinstate

내 대답에서 언급했듯이, 그것은 clang에서도 작동합니다.
Marqin

아, 나는 그것을 몰랐다. 그것은 사실상 a) __attribute__((cleanup()))준 표준 을 만드는 것처럼 요구 사항 a)를 훨씬 덜 심각하게 만든다 . 그러나 b)와 c)는 여전히 서있다.
cmaster-복원 monica

-2

이러한 코드에는 많은 이점이있을 수 있지만 불행히도 C 표준은이를 용이하게하기 위해 작성되지 않았습니다. 컴파일러는 역사적으로 표준이 요구하는 것 이상으로 효과적인 행동 보장을 제공하여 표준 C에서 가능한 것보다 훨씬 더 명확하게 그러한 코드를 작성할 수있게했지만, 컴파일러는 최근에 최적화라는 이름으로 그러한 보증을 취소하기 시작했습니다.

특히 많은 C 컴파일러는 역사적으로 (문서가 아닌 경우 설계 상) 두 구조 유형이 동일한 초기 시퀀스를 포함하는 경우 유형이 관련되지 않은 경우에도 두 유형 중 하나에 대한 포인터를 사용하여 해당 공통 시퀀스의 멤버에 액세스 할 수 있습니다. 또한 공통의 초기 시퀀스를 설정하기 위해 구조에 대한 모든 포인터는 동일합니다. 이러한 동작을 사용하는 코드는 그렇지 않은 코드보다 훨씬 깨끗하고 형식이 안전 할 수 있지만 불행히도 표준에서는 공통의 초기 시퀀스를 공유하는 구조를 동일한 방식으로 배치해야하지만 실제로는 코드를 사용하지 못합니다 한 유형의 포인터가 다른 유형의 초기 순서에 액세스합니다.

결과적으로 C로 객체 지향 코드를 작성하려면 C의 포인터 유형 규칙을 준수하기 위해 많은 후프를 뛰어 넘을 것인지 결정해야합니다 (이 결정을 일찍해야 함). 최신 컴파일러는 의도 한대로 작동하는 코드를 생성했거나 이전 스타일 포인터 동작을 지원하도록 구성된 컴파일러에서만 코드를 사용할 수 있어야한다는 요구 사항을 문서화 한 경우에도 무의미한 코드를 생성합니다 (예 : "-fno-strict-aliasing") 일부 사람들은 "-fno-strict-aliasing"을 악으로 간주하지만 "-fno-strict-aliasing"C를 "표준"C보다 몇 가지 목적으로 더 큰 의미를 제공합니다.그러나 다른 목적을 위해 중요 할 수있는 최적화를 희생합니다.

예를 들어, 기존 컴파일러에서 히스토리 컴파일러는 다음 코드를 해석합니다.

struct pair { int i1,i2; };
struct trio { int i1,i2,i3; };

void hey(struct pair *p, struct trio *t)
{
  p->i1++;
  t->i1^=1;
  p->i1--;
  t->i1^=1;
}

의 첫 번째 멤버를 증가시키고의 첫 번째 멤버의 *p가장 낮은 비트를 보완 *t한 다음의 첫 번째 멤버를 감소시키고의 첫 번째 멤버의 *p가장 낮은 비트를 보완하는*t . 최신 컴파일러는 다른 객체를 식별 p하고 t식별 하면 더 효율적 이지만 그렇지 않은 경우 동작을 변경 하는 코드 방식으로 작업 순서를 재정렬합니다 .

이 예제는 물론 의도적으로 고안되었으며 실제로는 한 유형의 포인터를 사용하여 다른 유형의 공통 초기 시퀀스의 일부인 멤버에 액세스하는 코드는 일반적으로 작동하지만 불행히도 이러한 코드가 실패하는 시점을 알 수있는 방법이 없기 때문에 불행히도 유형 기반 앨리어싱 분석을 비활성화하지 않으면 안전하게 사용할 수 없습니다.

두 포인터를 임의의 유형으로 바꾸는 것과 같은 기능을 작성하려는 경우 다소 덜 유망한 예가 발생합니다. 대다수의 "1990 년대 C"컴파일러에서 다음을 통해 달성 할 수 있습니다.

void swap_pointers(void **p1, void **p2)
{
  void *temp = *p1;
  *p1 = *p2;
  *p2 = temp;
}

그러나 표준 C에서는 다음을 사용해야합니다.

#include "string.h"
#include "stdlib.h"
void swap_pointers2(void **p1, void **p2)
{
  void **temp = malloc(sizeof (void*));
  memcpy(temp, p1, sizeof (void*));
  memcpy(p1, p2, sizeof (void*));
  memcpy(p2, temp, sizeof (void*));
  free(temp);
}

경우 *p2할당 된 스토리지에 보관되며, 임시 포인터가 할당 된 스토리지에 보관되지 않고,의 효과적인 유형의 *p2시도가 사용하는 임시 포인터의 유형 및 코드가 될 것이다 *p2임시 포인터에 맞지 않는 유형으로 type은 Undefined Behavior를 정의합니다. 컴파일러가 그러한 것을 알아 차릴 가능성은 거의 없지만, 현대의 컴파일러 철학은 프로그래머가 정의되지 않은 동작을 피할 것을 요구하기 때문에 할당 된 스토리지를 사용하지 않고 위의 코드를 작성하는 다른 안전한 수단을 생각할 수 없습니다 .


Downvoters : 의견이 있으십니까? 객체 지향 프로그래밍의 주요 측면은 여러 유형이 공통 측면을 공유하고 이러한 유형에 대한 포인터를 사용하여 해당 공통 측면에 액세스 할 수 있다는 것입니다. OP의 예제는 그렇게하지 않지만 "객체 지향"이라는 표면을 거의 긁지 않습니다. 히스토리 C 컴파일러는 오늘날의 표준 C에서 가능한 것보다 다형성 코드를 훨씬 더 깨끗한 방식으로 작성할 수 있습니다. 따라서 C에서 객체 지향 코드를 설계하려면 대상이되는 정확한 언어를 결정해야합니다. 사람들에게 어떤 측면에 동의하지 않습니까?
supercat

흠 ... 표준이 제공하는 보장으로 일반적인 초기 하위 시퀀스의 멤버에 깔끔하게 액세스 할 수없는 방법을 보여 주겠습니까? 이것이 계약 행위의 범위 내에서 대담의 악에 대한 당신의 열망이 이번에 달려 있다고 생각하기 때문에? ( 두 명의 다운 보더가 불쾌감을 느낀 점이 추측입니다.)
중복 제거기

OOP에는 반드시 상속이 필요하지 않으므로 두 구조체 사이의 호환성은 실제로 큰 문제가 아닙니다. 함수 포인터를 구조체에 넣고 특정 방식으로 해당 함수를 호출하여 실제 OO를 얻을 수 있습니다. 물론, foo_function(foo*, ...)C 의이 의사 -OO는 클래스처럼 보이는 특정 API 스타일 일뿐 아니라 추상 데이터 형식의 모듈 형 프로그래밍이라고하는 것이 더 적절합니다.
amon

@ 중복 제거기 : 표시된 예를 참조하십시오. 필드 "i1"은 두 구조의 공통 초기 시퀀스 멤버이지만, 코드가 "struct 쌍 *"을 사용하여 "struct trio"의 초기 멤버에 액세스하려고하면 최신 컴파일러가 실패합니다.
supercat

어떤 현대 C 컴파일러가 그 예에 실패합니까? 흥미로운 옵션이 필요하십니까?
중복 제거기

-3

다음 단계는 구조체 선언을 숨기는 것입니다. 이것을 .h 파일에 넣습니다.

typedef struct foo_s foo_t;

foo_t * foo_new(...);
void foo_destroy(foo_t *foo);
some_type foo_whatever(foo_t *foo, ...);
...

그런 다음 .c 파일에서

struct foo_s {
    ...
};

6
목표에 따라 다음 단계가 될 수 있습니다. 그것이 있든 없든, 이것은 원격으로 질문에 대답하려고 시도하지도 않습니다.
중복 제거기
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.