우리는 얼마나 많은 레벨의 포인터를 가질 수 있습니까?


443

*단일 변수에 몇 개의 포인터 ( )가 허용됩니까?

다음 예제를 살펴 보겠습니다.

int a = 10;
int *p = &a;

마찬가지로 우리는 할 수 있습니다

int **q = &p;
int ***r = &q;

등등.

예를 들어

int ****************zz;

582
그것이 당신에게 진짜 문제가된다면, 당신은 매우 잘못된 일을하고 있습니다.
ThiefMaster

279
두뇌가 폭발하거나 컴파일러가 녹을 때까지 포인터 수준을 계속 추가 할 수 있습니다.
JeremyP

47
포인터에 대한 포인터는 포인터 일 뿐이므로 이론적 인 제한이 없어야합니다. 어쩌면 컴파일러는 엄청나게 높은 한계를 넘어서 처리 할 수 ​​없을 것입니다.
Christian Rau

73
최신 C ++에서는 다음과 같은 것을 사용해야합니다.std::shared_ptr<shared_ptr<shared_ptr<...shared_ptr<int>...>>>
josefx

44
@josefx-이것은 C ++ 표준의 문제를 보여줍니다. 스마트 포인터를 올릴 방법이 없습니다. 예 (pow (std::shared_ptr, -0.3))<T> x;를 들어 -0.3 수준의 간접 지령 을 지원하기 위해 확장을 즉시 요구해야합니다 .
Steve314

답변:


400

C표준 지정 하한 :

번역 한계

276 구현은 다음 한계 중 하나 이상의 인스턴스를 하나 이상 포함하는 하나 이상의 프로그램을 번역하고 실행할 수 있어야한다. [...]

279 — 12 선언에서 산술, 구조, 공용체 또는 공백 유형을 수정하는 포인터, 배열 및 함수 선언자 (모든 조합)

상한은 구현에 따라 다릅니다.


121
C ++ 표준은 구현이 최소 256 개를 지원하는 것을 "권장"합니다. (가독성은 2 또는 3을 초과하지 않아야하며, 그보다 많을 경우는 예외입니다.)
James Kanze

22
이 한계는 단일 선언에서 몇 개나 되는가에 관한 것입니다. 여러 typedefs 를 통해 얼마나 많은 간접적 인을 달성 할 수 있는지에 대한 상한을 부과하지 않습니다 .
Kaz

11
@Kaz-그렇습니다. 그러나이 사양은 필수 하한을 지정하기 때문에 (pun 의도하지 않음) 모든 사양 호환 컴파일러가 지원 해야하는 효과적인 상한 입니다. 물론 공급 업체별 상한보다 낮을 수 있습니다. 다르게 표현하면 (OP의 질문에 맞추기 위해) 사양에서 허용 하는 최대 값입니다 (다른 것들은 벤더 마다 다를 있습니다). 접선에서 조금 벗어난 프로그래머 (적어도 일반적인 경우에는) 상한선 (공급 업체별 상한에 의존해야하는 정당한 이유가없는 한) ...
luis.espinal

5
또 다른 참고로, 긴 참조 역 참조 체인이있는 코드로 작업해야한다면 ( 자유롭게 도처에 후추를
뿌렸을

11
@ 베릴륨 : 일반적으로이 수치는 사전 표준화 소프트웨어를 조사한 결과입니다. 이 경우 아마도 공통 C 프로그램과 기존 C 컴파일러를 살펴 보았고 12 개 이상에서 문제가 있거나 12

155

실제로, C 프로그램은 일반적으로 무한 포인터 간접을 사용합니다. 하나 또는 두 개의 정적 레벨이 일반적입니다. 트리플 간접 지향은 드물다. 그러나 무한은 매우 일반적입니다.

무한 포인터 간접 지시는 물론 직접적인 선언자가 아닌 구조체의 도움으로 달성 할 수 없으며 불가능합니다. 그리고이 구조에 다른 데이터를 포함시킬 수있는 다른 레벨을 포함 할 수 있도록 구조체가 필요합니다.

struct list { struct list *next; ... };

지금 당신은 가질 수 있습니다 list->next->next->next->...->next. 이것은 실제로 여러 포인터 간접 참조 *(*(..(*(*(*list).next).next).next...).next).next입니다. 그리고 .next이것은 구조의 첫 번째 구성원 일 때 기본적으로 noop입니다. 따라서 이것을로 상상할 수 있습니다 ***..***ptr.

링크는 이와 같은 거대한 표현이 아닌 루프로 순회 할 수 있으며 구조를 쉽게 원형으로 만들 수 있기 때문에 이에 제한이 없습니다.

다시 말해, 링크 된 목록은 문제를 해결하기 위해 다른 수준의 간접 성을 추가하는 궁극적 인 예일 수 있습니다. 모든 푸시 작업에서 동적으로 수행하기 때문입니다. :)


48
그러나 완전히 다른 문제입니다. 다른 구조체에 대한 포인터를 포함하는 구조체는 포인터 포인터와 매우 다릅니다. int *****는 int ****와는 다른 유형입니다.
푹신한

12
"매우"다르지 않습니다. 그 차이는 푹신하다. 시맨틱보다 구문에 더 가깝습니다. 포인터 객체에 대한 포인터 또는 포인터를 포함하는 구조 객체에 대한 포인터? 같은 종류입니다. 목록의 10 번째 요소를 얻는 것은 10 단계의 주소 지정 어드레싱입니다. (물론, 무한 구조를 표현할 수있는 능력 있도록 불완전 구조체 형식을 통해 자신을 가리 키도록 수있는 구조체의 유형에 따라 다릅니다 list->nextlist->next->next같은 유형, 그렇지 않으면 우리는 무한 유형을 구성해야합니다.)
카즈

34
나는 "솜털"이라는 단어를 사용할 때 당신의 이름이 푹신하다는 것을 의식적으로 알지 못했습니다. 잠재 의식적인 영향? 그러나 나는 이전에 이런 식으로 단어를 사용했다고 확신합니다.
Kaz

3
또한 기계 언어에서는 LOAD R1, [R1]R1이 모든 단계에서 유효한 포인터라면 반복 할 수 있습니다 . "주소를 보유한 단어"이외의 유형은 없습니다. 선언 된 유형이 있는지 여부에 따라 간접 지시 및 수준이 결정되지 않습니다.
Kaz

4
구조가 원형이면 아닙니다. 경우 R1자체 포인트 다음 위치의 어드레스를 보유하고 LOAD R1, [R1]무한 루프에서 실행될 수있다.
Kaz

83

이론적으로 :

원하는 수준의 간접 참조를 가질 수 있습니다.

거의:

물론 메모리를 소비하는 것은 무기 한일 수 없으며 호스트 환경에서 사용 가능한 리소스로 인해 제한이 있습니다. 따라서 실제로 구현이 지원할 수있는 것에는 최대 한계가 있으며 구현시이를 적절하게 문서화해야합니다. 따라서 이러한 모든 아티팩트에서 표준은 최대 한계를 지정하지 않지만 하한을 지정합니다.

참조는 다음과 같습니다.

C99 표준 5.2.4.1 번역 한계 :

— 12 선언, 산술, 구조, 공용체 또는 공백 유형을 수정하는 포인터, 배열 및 함수 선언자 (모든 조합).

이것은 모든 구현 지원 해야하는 하한을 지정합니다 . 각주에서 표준은 다음과 같이 말합니다.

18) 구현시 가능한 한 고정 번역 제한을 부과하지 않아야합니다.


16
간접 지정은 어떤 스택에도 넘치지 않습니다!
바 실레 Starynkevitch

1
수정, 나는 매개 변수의 한계가 함수에 전달되는 한계로 q를 읽고 대답하는 잘못된 느낌을 가졌습니다. 왜 그런지 모르겠다!
Alok 저장 10.39에

2
@basile-스택 깊이가 파서에서 문제가 될 것으로 기대합니다. 많은 공식적인 파싱 알고리즘에는 핵심 구성 요소로 스택이 있습니다. 대부분의 C ++ 컴파일러는 재귀 강하의 변형을 사용하지만 프로세서 스택 (또는 프로세서 스택이있는 것처럼 작동하는 언어)에 의존합니다. 문법 규칙이 많이 중첩되면 스택이 더 깊어집니다.
Steve314

8
간접 지정은 어떤 스택에도 넘치지 않습니다! -> 아니요! 파서 스택이 오버플로 될 수 있습니다. 스택은 포인터 간접과 어떤 관련이 있습니까? 파서 스택!
Pavan Manjunath

경우 *행의 클래스의 숫자 과부하, 각 과부하가 행에 다른 유형의 개체를 반환, 다음과 같은 연쇄 함수 호출에 대한 유래가있을 수 있습니다.
Nawaz

76

사람들이 말했듯이, "이론적으로"제한은 없습니다. 그러나 흥미롭게도 g ++ 4.1.2로 이것을 실행했으며 최대 20,000 크기로 작동했습니다. 컴파일은 꽤 느렸으므로 더 높이려고하지 않았습니다. 따라서 g ++도 제한을 두지 않는다고 생각합니다. ( size = 10ptr.cpp가 즉시 명확하지 않으면 설정 하고 살펴 보십시오 .)

g++ create.cpp -o create ; ./create > ptr.cpp ; g++ ptr.cpp -o ptr ; ./ptr

create.cpp

#include <iostream>

int main()
{
    const int size = 200;
    std::cout << "#include <iostream>\n\n";
    std::cout << "int main()\n{\n";
    std::cout << "    int i0 = " << size << ";";
    for (int i = 1; i < size; ++i)
    {
        std::cout << "    int ";
        for (int j = 0; j < i; ++j) std::cout << "*";
        std::cout << " i" << i << " = &i" << i-1 << ";\n";
    }
    std::cout << "    std::cout << ";
    for (int i = 1; i < size; ++i) std::cout << "*";
    std::cout << "i" << size-1 << " << \"\\n\";\n";
    std::cout << "    return 0;\n}\n";
    return 0;
}

72
시도했을 때 98242 이상을 얻을 수 없었습니다. (필자는 Python에서 스크립트를 수행하여 *실패한 스크립트와 이전 스크립트를 얻을 때까지 수를 두 배로 늘 렸습니다 . 그런 다음 실패한 첫 번째 스크립트에 대해 해당 간격 동안 이진 검색을 수행했습니다. 전체 테스트는 1 초도 채 걸리지 않았습니다. 실행).
제임스 간제에게

63

확인하기 재미있다.

  • Visual Studio 2010 (Windows 7)의 경우이 오류가 발생하기 전에 1011 수준을 가질 수 있습니다.

    치명적인 오류 C1026 : 파서 ​​스택 오버플로, 프로그램이 너무 복잡

  • *충돌없이 gcc (우분투), 100k + ! 하드웨어가 한계라고 생각합니다.

(변수 선언만으로 테스트)


5
실제로 단항 연산자의 프로덕션은 올바른 재귀이므로 Shift-Reduce 파서는 축소 *하기 전에 모든 노드를 스택으로 옮깁니다.
Kaz

28

제한이 없습니다 . 여기에서 예제를 확인 하십시오 .

대답은 "포인터 수준"의 의미에 따라 다릅니다. "한 번의 선언으로 얼마나 많은 수준의 간접 지시를 가질 수 있습니까?" 답은 "최소한 12"입니다.

int i = 0;

int *ip01 = & i;

int **ip02 = & ip01;

int ***ip03 = & ip02;

int ****ip04 = & ip03;

int *****ip05 = & ip04;

int ******ip06 = & ip05;

int *******ip07 = & ip06;

int ********ip08 = & ip07;

int *********ip09 = & ip08;

int **********ip10 = & ip09;

int ***********ip11 = & ip10;

int ************ip12 = & ip11;

************ip12 = 1; /* i = 1 */

"프로그램을 읽기가 어려워지기 전에 얼마나 많은 수준의 포인터를 사용할 수 있는지"를 의미한다면 그것은 맛의 문제이지만 한계가 있습니다. 두 가지 수준의 간접 지향 (무엇에 대한 포인터)이 일반적입니다. 그 이상은 쉽게 생각하기가 조금 더 어려워집니다. 대안이 더 나 빠지지 않는 한 그렇게하지 마십시오.

"런타임에 얼마나 많은 수준의 포인터 간접 설정을 가질 수 있는지"라는 의미라면 제한이 없습니다. 이 점은 각 노드가 다음 노드를 가리키는 순환 목록에 특히 중요합니다. 프로그램은 포인터를 영원히 따라갈 수 있습니다.


7
컴파일러는 제한된 양의 메모리에있는 정보를 추적해야하기 때문에 거의 확실하게 한계가 있습니다. ( g++내 기계의 98242에서 내부 오류로 중단됩니다. 실제 한계는 기계 및로드에 따라 달라질 것으로 예상합니다. 또한 실제 코드에서는 이것이 문제가 될 것으로 예상하지 않습니다.)
James Kanze

2
네, @MatthieuM입니다. : 나는 방금 이론적으로 고려했다 :) 답변을 완료 해 주셔서 감사합니다 제임스
Nandkumar Tekale

3
글쎄, 링크 된리스트는 실제로 포인터를 가리키는 포인터가 아니며, 포인터를 포함하는 구조체에 대한 포인터이다. (또는
그중

1
@ Random832 : Nand는 "런타임에 얼마나 많은 포인터 간접 레벨을 가질 수 있는지"라고 말하면 포인터에 대한 포인터 (* n)에 대한 대화의 제한을 명시 적으로 제거하고있었습니다.
LarsH

1
나는 당신의 요점을 얻지 못했습니다 : ' 제한이 없습니다. 여기에서 예를 확인하십시오. '예는 제한이 없다는 증거는 아닙니다. 단지 12 개의 별 간접 지향이 가능하다는 것만 증명합니다. circ_listOP의 질문에 관한 예제 도 입증 하지 못했습니다. 포인터 목록을 탐색 할 수 있다는 사실은 컴파일러가 n 개의 별 간접 지시를 컴파일 할 수 있음을 의미하지는 않습니다.
Alberto

24

실제로 함수에 대한 포인터로 더 재미 있습니다.

#include <cstdio>

typedef void (*FuncType)();

static void Print() { std::printf("%s", "Hello, World!\n"); }

int main() {
  FuncType const ft = &Print;
  ft();
  (*ft)();
  (**ft)();
  /* ... */
}

여기에 설명 된 바와 같이 이것은 다음을 제공합니다.

안녕, 월드!
안녕, 월드!
안녕, 월드!

또한 런타임 오버 헤드가 필요하지 않으므로 컴파일러가 파일을 질식시킬 때까지 원하는만큼 스택 할 수 있습니다.


20

제한없습니다 . 포인터는 내용이 주소 인 메모리 덩어리입니다.
당신이 말했듯이

int a = 10;
int *p = &a;

포인터에 대한 포인터는 다른 포인터의 주소를 포함하는 변수이기도합니다.

int **q = &p;

다음 q은 주소를 보유하고있는 포인터를 가리키는 포인터 p입니다 a.

포인터에 대한 포인터에는 특별한 것이 없습니다.
따라서 다른 포인터의 주소를 보유하는 poniters 체인에는 제한이 없습니다.
즉.

 int **************************************************************************z;

허용됩니다.


17

모든 C ++ 개발자는 유명한 3 성 프로그래머 에 대해 들어야 합니다.

그리고 위장해야 할 마법의 "포인터 장벽"이있는 것 같습니다.

C2에서 인용 :

3 성 프로그래머

C 프로그래머를위한 등급 시스템. 포인터가 간접적 일수록 (즉, 변수 앞에 "*"가 많을수록) 평판이 높아집니다. 사실상 모든 비 사소한 프로그램은 포인터를 사용해야하기 때문에 별이없는 C 프로그래머는 거의 존재하지 않습니다. 대부분은 1 성급 프로그래머입니다. 예전에는 (어릴 때 젊었 기 때문에 이것들은 나에게는 예전의 것처럼 보였습니다), 때로는 3 성급 프로그래머가 수행 한 코드 조각을 찾아 경외감에 떨립니다. 어떤 사람들은 하나 이상의 간접적 인 수준에서 함수 포인터가 포함 된 3 성 코드를 보았다고 주장하기도했습니다. 나에게 UFO만큼이나 들렸다.


2
github.com/psi4/psi4public/blob/master/src/lib/libdpd/… 등은 4 성급 프로그래머가 작성했습니다. 그는 또한 내 친구이며 코드를 충분히 읽으면 4 별 가치가있는 이유를 이해할 수 있습니다.
Jeff

13

여기에는 두 가지 가능한 질문이 있습니다. C 유형에서 달성 할 수있는 포인터 간접 수준의 수준과 단일 선언자에 넣을 수있는 포인터 간접 수준의 수준.

C 표준을 사용하면 전자에 최대 값을 적용 할 수 있습니다 (최소값 제공). 그러나 여러 typedef 선언을 통해 우회 할 수 있습니다.

typedef int *type0;
typedef type0 *type1;
typedef type1 *type2; /* etc */

따라서 궁극적으로 이는 C 프로그램이 거부되기 전에 얼마나 큰 / 복잡한 크기가 될 수 있는지에 대한 구현 문제이며, 이는 컴파일러마다 매우 다릅니다.


4

임의의 수의 *로 유형을 생성하는 것은 템플릿 메타 프로그래밍으로 발생할 수있는 것임을 지적하고 싶습니다. 나는 내가 정확히하고있는 일을 잊었지만 재귀 T * 유형 을 사용하여 그들 사이에 어떤 종류의 메타 조작을하는 새로운 고유 유형을 생성 할 수 있다고 제안했습니다 .

템플릿 메타 프로그래밍은 광기의 속도가 느리기 때문에 수천 수준의 간접적 인 유형을 생성 할 때 변명 할 필요가 없습니다. 예를 들어, 기능 언어로서 템플릿 확장에 peano 정수를 매핑하는 편리한 방법입니다.


나는 당신의 대답을 완전히 이해하지 못한다는 것을 인정하지만, 탐구 할 새로운 영역이 주어졌습니다. :)
ankush981

3

2004 MISRA C 표준 의 규칙 17.5 는 2 단계 이상의 포인터 간접 지정을 금지합니다.


15
컴파일러가 아닌 프로그래머에게 권장되는 것입니다.
Cole Johnson

3
2 단계 이상의 포인터 간접 지시에 대한 규칙 17.5의 문서를 읽었습니다. 그리고 반드시 2 레벨 이상을 금지하지는 않습니다. 2 수준 이상이 "non-compliant"표준에 따르므로 판결을 준수해야한다고 명시되어 있습니다 . 판결에서 중요한 단어 나 문구는 "should"이 문장 의 단어 를 사용하는 것입니다 . Use of more than 2 levels of indirection can seriously impair the ability to understand the behavior of the code, and should therefore be avoided.이는 언어 표준에 의해 설정된 규칙과 반대로이 조직에서 설정 한 지침입니다.
Francis Cugler

1

실제 한계 와 같은 것은 없지만 한계가 있습니다. 모든 포인터는 일반적으로 힙이 아닌 스택에 저장되는 변수입니다 . 스택은 일반적으로 작습니다 (일부 연결 중에 크기를 변경할 수 있음). 따라서 4MB 스택이 있다고 가정 해 봅시다. 정상 크기입니다. 그리고 4 바이트 크기의 포인터가 있다고 가정 해 봅시다 (포인터 크기는 아키텍처, 대상 및 컴파일러 설정에 따라 다릅니다).

이 경우 4 MB / 4 b = 1024가능한 최대 수는 1048576이지만 다른 것들이 스택되어 있다는 사실을 무시해서는 안됩니다.

그러나 일부 컴파일러에는 최대 개수의 포인터 체인이있을 수 있지만 한계는 스택 크기입니다. 따라서 무한대와 연결하는 동안 스택 크기를 늘리고 해당 메모리를 처리하는 OS를 실행하는 무한대 메모리가있는 시스템을 사용하면 포인터 체인이 무제한이됩니다.

당신이 사용하는 경우 int *ptr = new int;와 그렇지 않다 힙에 포인터 넣어 일반적인 방법으로 제한 힙 크기가 아닌 스택 될 것입니다.

편집 그냥 깨달으십시오 infinity / 2 = infinity. 머신에 메모리가 더 있으면 포인터 크기가 증가합니다. 따라서 메모리가 무한대이고 포인터의 크기가 무한대이면 나쁜 소식입니다 ... :)


4
A) 포인터는 힙 ( new int*) 에 저장할 수 있습니다 . B) An int*과 A int**********는 적어도 합리적인 아키텍처에서 같은 크기입니다.

@rightfold A) 예 포인터를 힙에 저장할 수 있습니다. 그러나 다음 이전 포인터를 가리키는 포인터를 보유하는 컨테이너를 만드는 것과는 매우 다릅니다. 물론 B) int*과는 int**********같은 크기를 가지고, 나는 서로 다른이 있다고하지 않았다.
ST3

2
그런 다음 스택 크기가 원격으로 어떻게 관련되는지 알 수 없습니다.

@rightfold 모든 데이터가 힙에 있고 스택에있을 때 일반적인 데이터 배포 방법에 대해 생각 했습니다. 해당 데이터에 대한 포인터 일뿐입니다. 그것은 것이 일반적인 방법,하지만 난 그것을 스택 포인터를 넣어하는 것이 가능하다는 것을 동의합니다.
ST3

"물론 int *와 int **********는 같은 크기를가집니다"-표준은 보장하지 않습니다 (그러나 그것이 사실이 아닌 플랫폼은 알지 못합니다).
Martin Bonner는 Monica

0

포인터를 저장하는 위치에 따라 다릅니다. 그것들이 스택에 있다면 당신은 매우 낮은 한계 를 가지고 있습니다 . 힙에 저장하면 제한이 훨씬 높습니다.

이 프로그램을보십시오 :

#include <iostream>

const int CBlockSize = 1048576;

int main() 
{
    int number = 0;
    int** ptr = new int*[CBlockSize];

    ptr[0] = &number;

    for (int i = 1; i < CBlockSize; ++i)
        ptr[i] = reinterpret_cast<int *> (&ptr[i - 1]);

    for (int i = CBlockSize-1; i >= 0; --i)
        std::cout << i << " " << (int)ptr[i] << "->" << *ptr[i] << std::endl;

    return 0;
}

그것은 1M 포인터를 생성하고 체인에서 첫 번째 변수로가는 것이 무엇인지 쉽게 알 수있는 지점을 보여줍니다 number.

BTW. 92KRAM을 사용 하므로 얼마나 깊이 갈 수 있는지 상상하십시오.

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