C는 그다지 어렵지 않습니다.void ( * ( * f [ ] ) )
오늘 사진을 봤는데 설명을 해주시면 감사하겠습니다.여기 그림이 있습니다.
나는 이것이 혼란스럽다는 것을 알았고, 그러한 코드들이 과연 실용적인지 궁금했다.사진을 검색해 보니 이 Reddit 엔트리에서 다른 사진이 검색되어 있습니다.이 사진은 다음과 같습니다.
그럼 이 '스피럴하게 읽기'가 유효한 건가요?C 컴파일러는 이렇게 해석합니까?
이 이상한 코드에 대한 간단한 설명이 있었으면 좋겠어요.
다른 것 말고도, 이런 종류의 코드가 도움이 될까요?그렇다면 언제 어디서?
"나선형 규칙"에 대한 질문이 있지만, 나는 단지 그것이 어떻게 적용되는지, 혹은 그 규칙에서 어떻게 표현되는지에 대한 질문만 하는 것이 아니다.나는 그러한 표현의 사용이나 소용돌이 법칙의 타당성에 대해서도 의문을 제기하고 있다.이에 대해 이미 좋은 답변이 올라오고 있습니다.
"spiral" 규칙은 다음과 같은 우선 순위 규칙에서 제외됩니다.
T *a[] -- a is an array of pointer to T
T (*a)[] -- a is a pointer to an array of T
T *f() -- f is a function returning a pointer to T
T (*f)() -- f is a pointer to a function returning T
첨자[]
및 함수 호출()
연산자는 단항보다 우선도가 높다*
,그렇게*f()
라고 해석됩니다.*(f())
그리고.*a[]
라고 해석됩니다.*(a[])
.
따라서 배열에 대한 포인터 또는 함수에 대한 포인터를 원하는 경우 명시적으로 그룹화할 필요가 있습니다.*
에서와 같이 식별자를 사용하여(*a)[]
또는(*f)()
.
그러면 알 수 있을 거야a
그리고.f
단순한 식별자보다 더 복잡한 표현일 수 있습니다.T (*a)[N]
,a
단순한 식별자일 수도 있고, 다음과 같은 함수 호출일 수도 있습니다.(*f())[N]
(a
->f()
또는 다음과 같은 어레이일 수 있습니다.(*p[M])[N]
, (a
->p[M]
또는 다음과 같은 함수에 대한 포인터의 배열일 수 있습니다.(*(*p[M])())[N]
(a
->(*p[M])()
) 등입니다.
간접 연산자가 다음 명령을 수행했으면 합니다.*
단항이 아닌 포스트픽스(postfix)로 선언문을 왼쪽에서 오른쪽으로 읽기가 다소 쉬워집니다(void f[]*()*();
확실히 보다 잘 흐르다void (*(*f[])())()
), 하지만 그렇지 않습니다.
이와 같은 애매한 선언이 발생했을 경우 맨 왼쪽의 식별자를 찾아 위의 우선순위 규칙을 적용하여 임의의 함수 파라미터에 재귀적으로 적용합니다.
f -- f
f[] -- is an array
*f[] -- of pointers ([] has higher precedence than *)
(*f[])() -- to functions
*(*f[])() -- returning pointers
(*(*f[])())() -- to functions
void (*(*f[])())(); -- returning void
그signal
표준 라이브러리의 함수는 아마도 이런 종류의 정신 이상에 대한 유형 표본일 것입니다.
signal -- signal
signal( ) -- is a function with parameters
signal( sig, ) -- sig
signal(int sig, ) -- which is an int and
signal(int sig, func ) -- func
signal(int sig, *func ) -- which is a pointer
signal(int sig, (*func)(int)) -- to a function taking an int
signal(int sig, void (*func)(int)) -- returning void
*signal(int sig, void (*func)(int)) -- returning a pointer
(*signal(int sig, void (*func)(int)))(int) -- to a function taking an int
void (*signal(int sig, void (*func)(int)))(int); -- and returning void
이 시점에서 대부분의 사람들은 "typedefs를 사용한다"고 말하는데, 이는 분명 옵션입니다.
typedef void outerfunc(void);
typedef outerfunc *innerfunc(void);
innerfunc *f[N];
그렇지만.....
어떻게 사용하시겠습니까? f
표현으로요?포인터의 배열인 것을 알고 있습니다만, 올바른 기능을 실행하기 위해서 어떻게 사용합니까?당신은 타이피피스를 검토해서 정확한 구문을 찾아내야 한다.이와는 대조적으로, "나체" 버전은 꽤 눈에 띄지만, 정확히 어떻게 사용하는지 알려준다. f
표현으로 표현하다(예:(*(*f[i])())();
(어느 함수도 인수를 받지 않는다고 가정합니다).
복잡한 선언의 의미를 찾는 데 도움이 되는 "시계/나선형 규칙"이라는 규칙이 있습니다.
c-faq부터:
다음의 3개의 간단한 순서가 있습니다.
알 수 없는 요소부터 시작하여 나선/시계 방향으로 이동합니다. 다음 요소를 전달할 때 해당 영문 문장으로 대체합니다.
[X]
또는[]
=> 어레이 X 사이즈...또는 어레이의 정의되지 않은 크기...
(type1, type2)
=> 함수가 type1 및 type2를 통과하면 반환...
*
=> 포인터:모든 토큰이 덮일 때까지 나선/시계 방향으로 계속 이 작업을 수행하십시오.
항상 괄호 안에 있는 것을 먼저 해결하세요!
위의 링크에서 예를 확인할 수 있습니다.
또, 다음의 Web 사이트도 준비되어 있습니다.
C 선언을 입력하면 영어의 의미가 나타납니다.위해서
void (*(*f[])())()
출력:
무효를 반환하는 함수로 포인터를 반환하는 함수에 대한 포인터의 배열로서 f를 선언한다.
편집:
Random 832의 코멘트에서 지적된 바와 같이 Spiral 규칙은 배열 배열을 다루지 않으며 이러한 선언의 대부분에서 잘못된 결과를 초래합니다.예를 들어,int **x[1][2];
소용돌이 법칙은 ...라는 사실을 무시한다.[]
보다 우선도가 높다*
.
배열 배열 앞에 있는 경우 완화곡선 규칙을 적용하기 전에 먼저 명시적 괄호를 추가할 수 있습니다.예를 들어 다음과 같습니다.int **x[1][2];
와 같다int **(x[1][2]);
(또한 유효한 C)는 우선순위로 인해 Spiral Rule은 올바른 영문 선언인 "x is a array 1 of pointer to int"로 올바르게 읽힙니다.
이 문제는 James Kanze의 답변에서도 다루어졌습니다(댓글에 해커가 지적).
그럼 이 '스피럴하게 읽기'가 유효한 건가요?
Spiral 규칙을 적용하거나 cdecl을 사용하는 것은 항상 유효한 것은 아닙니다.경우에 따라서는 양쪽 모두 실패.스파이럴 룰은 많은 경우에 효과가 있지만 보편적이지는 않다.
복잡한 선언을 해독하려면 다음 두 가지 간단한 규칙을 기억하십시오.
선언을 항상 안쪽에서 바깥쪽으로 읽습니다. 괄호가 있는 경우 가장 안쪽에서 시작합니다.선언할 식별자를 찾아 거기에서 선언문을 해독합니다.
선택사항이 있을 때는 항상 찬성하고 넘깁니다.
*
에 붙습니다.또, 「 」는 「 」입니다.[]
그 뒤에 있는 식별자는 포인터가 아닌 배열을 나타냅니다. 「」의 경우는, 「」입니다.*
에 붙습니다.또, 「 」는 「 」입니다.()
.(부모는 항상 위해 할 수 (는 priority가 우선입니다.)[]
★★★★★★★★★★★★★★★★★」()
에 걸쳐서*
. )
이 규칙은 실제로 식별자의 한쪽에서 다른 쪽으로 지그재그로 이동합니다.
이제 간단한 선언문을 해독하는 중
int *a[10];
규칙을 적용하는 중:
int *a[10]; "a is"
^
int *a[10]; "a is an array"
^^^^
int *a[10]; "a is an array of pointers"
^
int *a[10]; "a is an array of pointers to `int`".
^^^
복잡한 선언문을 해독해 봅시다
void ( *(*f[]) () ) ();
위의 규칙을 적용하면 됩니다.
void ( *(*f[]) () ) (); "f is"
^
void ( *(*f[]) () ) (); "f is an array"
^^
void ( *(*f[]) () ) (); "f is an array of pointers"
^
void ( *(*f[]) () ) (); "f is an array of pointers to function"
^^
void ( *(*f[]) () ) (); "f is an array of pointers to function returning pointer"
^
void ( *(*f[]) () ) (); "f is an array of pointers to function returning pointer to function"
^^
void ( *(*f[]) () ) (); "f is an array of pointers to function returning pointer to function returning `void`"
^^^^
다음은 작업 방법을 보여주는 GIF입니다(그림 클릭 시 확대보기).
여기에 언급된 규칙들은 K.N KING의 책 C Programming A Modern Approach에서 따온 것이다.
C에서는 선언이 사용법을 반영하고 있습니다.표준에서는 그렇게 정의되어 있습니다.선언:
void (*(*f[])())()
하다, 표현하다라는 입니다.(*(*f[i])())()
타입의 결과를 낳다void
'먹다,먹다,먹다'라는 뜻이에요
f
인덱스를 작성할 수 있으므로 배열이어야 합니다.f[i]
의
f
참조 해제 가능하기 때문에 포인터여야 합니다.*f[i]
이러한 포인터는 다음과 같이 호출할 수 있으므로 인수를 사용하지 않는 함수에 대한 포인터여야 합니다.
(*f[i])()
이러한 함수는 참조 해제할 수 있으므로 결과도 포인터여야 합니다.
*(*f[i])()
이러한 포인터는 인수를 사용하지 않는 함수에 대한 포인터여야 합니다.이 포인터는 다음과 같이 호출할 수 있기 때문입니다.
(*(*f[i])())()
는 반환해야 .
void
"나선형 규칙"은 같은 것을 이해하는 다른 방법을 제공하는 니모닉일 뿐입니다.
저는 우연히 수년 전(머리카락이 많았던 시절)에 썼던 나선형의 법칙의 원저자가 되었고, 그것이 cfaq에 추가되었을 때 영광이었습니다.
저는 학생들과 동료들이 C 선언문을 쉽게 읽을 수 있도록 하기 위해 스파이럴 규칙을 작성했습니다.즉, cdecl.org 등의 소프트웨어 도구를 사용할 필요가 없습니다.나선규칙이 C표현을 해석하는 표준적인 방법이라고 선언할 의도는 없었습니다.하지만 이 규칙이 말 그대로 수천 명의 C 프로그래밍 학생과 실무자에게 도움이 되었다는 것을 알게 되어 기쁩니다!
기록을 위해.
리누스 토발즈(제가 매우 존경하는 분)를 포함한 많은 사이트에서 저의 나선적 규칙이 "붕괴"되는 상황이 있다는 것은 여러 번 "올바르게" 확인되었습니다.가장 일반적인 것은 다음과 같습니다.
char *ar[10][10];
이 스레드의 다른 사용자가 지적한 바와 같이 어레이가 발생했을 때 다음과 같이 쓰여진 것처럼 모든 인덱스를 소비하도록 규칙을 업데이트할 수 있습니다.
char *(ar[10][10]);
나선형의 법칙에 따라, 나는 다음과 같이 할 것이다.
"ar는 10x10 문자 포인터의 2차원 배열입니다."
스파이럴 룰이 C를 배우는데 유용했으면 좋겠어!
추신:
"C는 딱딱하지 않다" 이미지가 마음에 들어요:)
- 로 하다
(*(*f[]) ()) ()
void
>>
(*(*f[]) ())
=() = 보이드
()
>>
(*f[]) ()
=가 반환 = 함수가 반환(복귀) = 함수가 반환(복귀)
*
>>
(*f[])
) = ( 리턴 ) ( ) = ( 리턴(으)로 포인터)
()
>>
f[]
= returning ( returning (function returning (function returning (function returning (function returning (function (
*
>>
f
가 (로) [] = (함수가 (복귀)로)
[ ]
>>
- f = 의 배열(복귀(복귀(복귀(복귀(복귀)))))
이 선언에서는 각 괄호 레벨의 양쪽에 연산자가 한 명밖에 없기 때문에 "나선형"일 뿐입니다.으로 진행한다고 주장하는 것은 '에서 배열과 포인터를 가며 합니다.int ***foo[][][]
실제로는 모든 어레이 레벨이 포인터 레벨보다 우선합니다.
이런 건축물이 실생활에 도움이 될지는 의문입니다.일반 개발자(컴파일러 라이터에게 있어서는 괜찮은 것 같다)의 인터뷰 질문이라고 하는 것조차 싫어합니다.대신 typedefs를 사용해야 합니다.
우연한 사실로서, 당신은 C 선언이 어떻게 읽혀지는지를 설명하는 실제 단어가 영어에 있다는 것을 알면 재미있을지도 모른다.쌍방향으로 말하자면, 오른쪽에서 왼쪽으로 번갈아 가며 말하는 것입니다.
레퍼런스:Van der Linden, 1994 - 76 페이지
이 기능의 유용성에 대해서는 셸 코드를 사용하면 다음과 같은 구성을 많이 볼 수 있습니다.
int (*ret)() = (int(*)())code;
ret();
구문적으로는 그다지 복잡하지 않지만, 이 특정 패턴은 자주 나타납니다.
SO 질문의 보다 완전한 예를 제시하겠습니다.
따라서 원본 그림에서 어디까지나 유용성은 의문이지만(어떤 생산 코드도 대폭 단순화해야 한다고 제안합니다), 구문적인 구성도 상당히 많이 나타납니다.
선언문
void (*(*f[])())()
그저 애매한 표현일 뿐이다.
Function f[]
와 함께
typedef void (*ResultFunction)();
typedef ResultFunction (*Function)();
실제로는 ResultFunction과 Function 대신 보다 알기 쉬운 이름이 필요합니다.가능하다면 파라미터 목록도 다음과 같이 지정합니다.void
.
Bruce Eckel이 설명한 방법이 유용하고 따르기 쉽다는 것을 알게 되었습니다.
함수 포인터 정의
인수 및 반환값이 없는 함수에 대한 포인터를 정의하려면 다음과 같이 입력합니다.
void (*funcPtr)();
이와 같이 복잡한 정의를 볼 때, 그것을 공격하는 가장 좋은 방법은 중간에서 시작하여 원하는 방향으로 나아가는 것입니다."Starting in the middle"은 변수 이름 funcPtr에서 시작하는 것을 의미합니다."Working your out"은 오른쪽에서 가장 가까운 항목(이 경우 아무 것도 없습니다.오른쪽 괄호도 짧게 하고 왼쪽(아스터리스크로 나타나는 포인터), 오른쪽(인수가 필요 없는 함수를 나타내는 빈 인수 리스트), 왼쪽(void)을 보는 것을 의미합니다.ction에 반환값이 없습니다).이 좌우운동은 대부분의 선언에서 작동합니다.
리뷰하려면 "중간에 시작" ("funcPtr은 ...")오른쪽(아무것도 없습니다)으로 이동하고 "*" ("..." 포인터)를 찾은 후 오른쪽으로 이동하여 빈 인수 목록("..." 함수 "..."를 찾은 후 왼쪽으로 이동하여 void("funcPtr는 재미있는 포인터입니다")을 찾습니다.인수를 받지 않고 무효를 반환하는 이온).
*funcPtr에 괄호가 필요한 이유는 무엇인지 궁금할 수 있습니다.사용하지 않으면 컴파일러에 다음과 같이 표시됩니다.
void *funcPtr();
변수를 정의하는 것이 아니라 함수를 선언합니다(void*를 반환).컴파일러는 선언이나 정의가 어떤 것인지 알아냈을 때와 같은 프로세스를 거치는 것으로 생각할 수 있습니다.이 괄호는 "맞추기"를 위해 필요합니다.따라서 오른쪽에서 빈 인수 목록을 찾는 대신 왼쪽으로 돌아가서 "*"를 찾습니다.
복잡한 선언과 정의
한편, C 및 C++ 선언 구문이 어떻게 기능하는지 알게 되면 훨씬 더 복잡한 항목을 만들 수 있습니다.예:
//: C03:ComplicatedDefinitions.cpp /* 1. */ void * (*(*fp1)(int))[10]; /* 2. */ float (*(*fp2)(int,int,float))(int); /* 3. */ typedef double (*(*(*fp3)())[10])(); fp3 a; /* 4. */ int (*(*f4())[10])(); int main() {} ///:~
각 항목을 살펴보고 오른쪽-왼쪽 가이드라인을 사용하여 파악합니다.숫자 1은 "fp1은 정수 인수를 사용하여 10개의 보이드 포인터 배열로 포인터를 반환하는 함수에 대한 포인터"라고 말합니다.
숫자 2는 "fp2는 세 개의 인수(int, int 및 float)를 사용하고 정수 인수를 사용하여 float를 반환하는 함수에 대한 포인터입니다."라고 말합니다.
복잡한 정의를 많이 작성하는 경우 typedef를 사용하는 것이 좋습니다.3번은 typedef가 매번 복잡한 설명을 타이핑하여 저장하는 방법을 보여줍니다."fp3는 인수를 사용하지 않고 인수를 사용하지 않고 더블을 반환하는 함수에 대한 포인터 10개 배열로 반환되는 함수에 대한 포인터입니다."라고 되어 있습니다.그런 다음 "a is these fp3 type"이라고 표시됩니다.typedef는 일반적으로 간단한 것부터 복잡한 설명을 작성할 때 유용합니다.
숫자 4는 변수 정의가 아닌 함수 선언입니다."f4는 정수를 반환하는 함수에 대한 10개의 포인터 배열로 포인터를 반환하는 함수"라고 되어 있습니다.
당신은 이런 복잡한 선언과 정의를 필요로 하는 경우는 거의 없을 것입니다.하지만, 만약 여러분이 그것들을 알아내는 연습을 한다면, 여러분은 현실에서 마주칠 수 있는 약간 복잡한 것들로 인해 약간의 방해도 받지 않을 것입니다.
취득처:C++ 제1권, 제2판, 제3장, Bruce Ekkel의 "기능 주소" 섹션의 생각.
C C는 C를 선언합니다.
그리고 우선순위는 결코 의심할 여지가 없을 것이다.
접미사'로.
그리고 두 세트를 안쪽에서 밖으로 읽으세요.
나
물론 괄호로 수정되는 경우는 제외한다.또한 이러한 선언 구문은 해당 변수를 사용하여 기본 클래스의 인스턴스를 가져오는 구문을 그대로 반영합니다.
정말이지, 이것은 한 눈에 익히는 것이 어렵지 않다. 단지 기꺼이 기술을 연습하는 데 시간을 들여야 한다.만약 당신이 다른 사람이 쓴 C코드를 유지하거나 개조할 것이라면, 그 시간을 투자할 가치가 있습니다.아직 배우지 않은 다른 프로그래머들을 놀래키기 위한 재미있는 파티 트릭이기도 하다.
자신의 코드에 대해서: 항상 그렇듯이, 그것이 표준적인 관용어(스트링 복사 루프 등)가 된 극히 일반적인 패턴이 아니라면, 어떤 것이 원라이너로 쓰여져야 한다는 것을 의미하지는 않는다.여러분과 여러분을 따르는 사람들은 "한 번에" 이러한 것들을 생성하고 해석하는 능력에 의존하기 보다는, 층층이 쌓은 타이프 정의와 단계별 유기로 복잡한 활자를 만든다면 훨씬 더 행복할 것입니다.퍼포먼스는 마찬가지로 우수하며 코드 가독성과 유지보수성도 크게 향상됩니다.
자, 더 나쁠 수도 있었어요.다음과 같은 내용으로 시작하는 법적 PL/I 진술이 있었습니다.
if if if = then then then = else else else = if then ...
언급URL : https://stackoverflow.com/questions/34548762/c-isnt-that-hard-void-f
'IT이야기' 카테고리의 다른 글
vue 구성 요소에서 속성 감시 (0) | 2022.06.20 |
---|---|
char는 항상 8비트를 가지고 있습니까? (0) | 2022.06.20 |
"%"가 뭐죠?*s"는 printf의 의미입니까? (0) | 2022.06.20 |
왜 모바일에서는 키다운이 작동하지 않는가?버전 2 (0) | 2022.06.19 |
C 컴파일 오류: "변수 크기 개체를 초기화할 수 없습니다." (0) | 2022.06.19 |