Сколько уровней указателей мы можем иметь?


444

Сколько указателей ( *) разрешено в одной переменной?

Давайте рассмотрим следующий пример.

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

Точно так же мы можем иметь

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

и так далее.

Например,

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

582
Если это когда-нибудь станет для вас реальной проблемой, вы делаете что-то очень неправильное.
ThiefMaster

279
Вы можете продолжать добавлять уровни указателей, пока ваш мозг не взорвется или компилятор не растает - в зависимости от того, что произойдет раньше.
JeremyP

47
Так как указатель на указатель снова, ну, просто указатель, не должно быть никакого теоретического ограничения. Может быть, компилятор не сможет справиться с этим сверх какого-то смехотворно высокого предела, но хорошо ...
Кристиан Рау

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стандарт устанавливает нижний предел:

5.2.4.1 Пределы перевода

276 Реализация должна иметь возможность переводить и выполнять по крайней мере одну программу, которая содержит по крайней мере один экземпляр каждого из следующих ограничений: [...]

279 - 12 указателей, массивов и функций объявления (в любых комбинациях), изменяющих арифметический, структурный, объединенный или пустой тип в объявлении

Верхний предел зависит от реализации.


121
Стандарт C ++ «рекомендует», чтобы реализация поддерживала не менее 256. (Читаемость рекомендует, чтобы вы не превышали 2 или 3, и даже тогда: более одного должно быть исключительным.)
Джеймс Канз

22
Это ограничение о том, сколько в одной декларации; это не налагает верхнюю границу на то, сколько косвенности вы можете достичь через несколько typedefs.
Каз

11
@ Kaz - да, это правда. Но поскольку спецификация (без каламбура) задает обязательный нижний предел, это эффективная верхняя граница, которую должны поддерживать все совместимые со спецификацией компиляторы . Конечно, она может быть ниже, чем верхняя граница для конкретного поставщика. Перефразируя иначе (чтобы выровнять это с вопросом ОП), это максимум, разрешенный спецификацией (все остальное будет зависеть от поставщика). Немного не касаясь, программисты должны (по крайней мере в общем случае) воспринимать это как свои верхний предел (если только у них нет веской причины полагаться на верхнюю границу, зависящую от поставщика) ... я думаю.
luis.espinal

5
С другой стороны, я бы начал резать себя, если бы мне приходилось работать с кодом, который имел длинные цепочки разыменовывания (особенно, когда он был повсеместно
обострен

11
@beryllium: Обычно эти цифры получены в результате прохождения предварительного стандартизации программного обеспечения. В этом случае, по-видимому, они рассмотрели обычные программы на C и существующие компиляторы C и нашли, по крайней мере, один компилятор, у которого были бы проблемы с более чем 12 и / или никакими программами, которые сломались бы, если вы ограничили его до 12.

155

На самом деле, программы на C обычно используют бесконечное указание на указатель. Один или два статических уровня являются общими. Тройная косвенность редка. Но бесконечность очень распространена.

Бесконечная косвенность указателя достигается с помощью структуры, конечно, а не с помощью прямого декларатора, что было бы невозможно. И структура необходима для того, чтобы вы могли включить другие данные в эту структуру на разных уровнях, где это может закончиться.

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

Теперь вы можете иметь list->next->next->next->...->next. Это действительно просто несколько indirections указатель: *(*(..(*(*(*list).next).next).next...).next).next. И, в .nextсущности, это первый шаг, когда он является первым членом структуры, поэтому мы можем представить это как ***..***ptr.

На самом деле нет никаких ограничений в этом, потому что ссылки можно обходить с помощью цикла, а не гигантского выражения, подобного этому, и, кроме того, структуру можно легко сделать круглой.

Таким образом, другими словами, связанные списки могут быть окончательным примером добавления другого уровня косвенности для решения проблемы, поскольку вы делаете это динамически с каждой операцией push. :)


48
Это совсем другая проблема - структура, содержащая указатель на другую структуру, сильно отличается от указателя-указателя. Тип int ***** отличается от типа int ****.
пушистый

12
Это не "очень" отличается. Разница пушистая. Это ближе к синтаксису, чем к семантике. Указатель на объект указателя или указатель на объект структуры, который содержит указатель? Это то же самое. Достижение до десятого элемента списка - это десять уровней адресации. (Конечно, способность выражать бесконечную структуру зависит от того, может ли тип структуры указывать на себя через неполный тип структуры, так что это один list->nextи list->next->nextтот же тип; в противном случае нам пришлось бы создавать бесконечный тип.)
Каз

34
Я сознательно не заметил, что ваше имя пушистое, когда я использовал слово «пушистый». Подсознательное влияние? Но я уверен, что использовал это слово раньше.
Каз

3
Также помните, что в машинном языке вы можете просто повторять что-то вроде LOAD R1, [R1]тех пор, пока R1 является действительным указателем на каждом шаге. Здесь не задействованы никакие типы, кроме слова, содержащего адрес. Существуют ли объявленные типы или нет, не определяет косвенность и количество уровней.
Каз

4
Нет, если структура круглая. Если R1содержит адрес местоположения, которое указывает на себя, то LOAD R1, [R1]может выполняться в бесконечном цикле.
Kaz

83

Теоретически:

Вы можете иметь столько уровней косвенности, сколько захотите.

Практически:

Конечно, ничто из того, что потребляет память, не может быть неопределенным, будут ограничения из-за ресурсов, доступных в среде хоста. Таким образом, практически существует максимальный предел того, что может поддерживать реализация, и реализация должна надлежащим образом это документировать. Таким образом, во всех таких артефактах стандарт не определяет максимальный предел, но он определяет нижние пределы.

Вот ссылка:

Стандарт С99 5.2.4.1 Пределы перевода:

- 12 описателей указателей, массивов и функций (в любых комбинациях), изменяющих арифметику, структуру, объединение или тип void в объявлении.

Это определяет нижний предел, который должна поддерживать каждая реализация . Обратите внимание, что в футоне примечание гласит:

18) Реализации должны по возможности избегать введения фиксированных лимитов перевода.


16
Направления не переполняют стеки!
Василий Старынкевич

1
Исправлено, у меня было странное чувство чтения и ответа на q как предела параметров, передаваемых в функцию. Я не знаю почему ?!
Alok Save

2
@ basile - я ожидаю, что глубина стека будет проблемой в парсере. Многие формальные алгоритмы синтаксического анализа имеют стек в качестве ключевого компонента. Большинство компиляторов C ++, вероятно, используют вариант рекурсивного спуска, но даже это зависит от стека процессора (или, педантично, от языка, действующего так, как если бы был стек процессора). Большее вложение правил грамматики означает более глубокий стек.
Steve314

8
Направления не переполняют стеки! -> Нет! стек анализатора может переполниться. Как стек соотносится с косвенным указателем? Парсер стек!
Паван Манджунат

Если *перегружено для ряда классов в строке, и каждая перегрузка возвращает объект другого типа в строке, то может быть переполнение стека для таких связанных вызовов функций.
Наваз

76

Как уже говорили люди, нет предела «в теории». Однако из интереса я запустил это с g ++ 4.1.2, и он работал с размером до 20000. Компиляция была довольно медленной, поэтому я не пытался выше. Так что я думаю, что g ++ тоже не накладывает никаких ограничений. (Попробуйте установить size = 10и посмотреть в ptr.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, удваивая число *до тех пор, пока не получил тот, который не прошел, и предыдущий, который прошел; затем я выполнил бинарный поиск по этому интервалу для первого, который не прошел. Весь тест занял менее секунды бежать.)
Джеймс Канз

63

Звучит весело, чтобы проверить.

  • Visual Studio 2010 (в Windows 7), вы можете иметь 1011 уровней, прежде чем получить эту ошибку:

    фатальная ошибка C1026: переполнение стека анализатора, слишком сложная программа

  • gcc (Ubuntu), 100к + *без сбоев! Я предполагаю, что аппаратное обеспечение - предел здесь.

(проверено только с объявлением переменной)


5
Действительно, унарные операторы производят правильную рекурсию, а это означает, что синтаксический анализатор с уменьшением сдвига сместит все *узлы в стек, прежде чем сможет сделать сокращение.
Каз

29

Там нет предела, посмотрите пример здесь .

Ответ зависит от того, что вы подразумеваете под «уровнями указателей». Если вы имеете в виду «Сколько уровней косвенности вы можете иметь в одной декларации?» ответ "По крайней мере, 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 на моей машине. Я ожидаю, что фактический лимит будет зависеть от машины и нагрузки. Я также не ожидаю, что это будет проблемой в реальном коде.)
Джеймс Канз

2
Да, @MatthieuM. : Я только что
обдумал

3
Ну, связанные списки на самом деле не указатель на указатель, это указатель на структуру, которая содержит указатель (либо это, либо вы в итоге выполняете много ненужного приведения)
Random832

1
@ Random832: Нанд сказал: «Если вы имеете в виду« Сколько уровней перенаправления указателей вы можете иметь во время выполнения »), то он явно снимал ограничение, состоящее в том, чтобы просто говорить об указателях на указатели (* n).
LarsH

1
Я не понимаю вашу точку зрения: « Нет предела, посмотрите пример здесь. «Этот пример не является доказательством того, что нет предела. Это только доказывает, что возможно 12-звездное косвенное обращение. Ни один из них не доказывает, что circ_listпример, касающийся вопроса ОП: тот факт, что вы можете просматривать список указателей, не означает, что компилятор может компилировать n-звездную косвенность.
Альберто

24

Это даже смешнее с указателем на функции.

#include <cstdio>

typedef void (*FuncType)();

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

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

Как показано здесь, это дает:

Привет мир!
Привет мир!
Привет мир!

И это не связано с какими-либо накладными расходами во время выполнения, так что вы, вероятно, можете составлять их столько, сколько захотите ... пока ваш компилятор не захлебнется файлом.


21

Там нет предела . Указатель - это фрагмент памяти, содержимое которого является адресом.
Как вы сказали

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

Указатель на указатель также является переменной, которая содержит адрес другого указателя.

int **q = &p;

Здесь qуказатель на указатель, содержащий адрес, pкоторый уже содержит адрес a.

В указателе на указатель нет ничего особенного.
Таким образом, нет никакого ограничения на цепочку понитеров, которые содержат адрес другого указателя.
то есть.

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

позволено.


18

Каждый разработчик C ++ должен был услышать о (не) известном Трехзвездочном программисте

И действительно, кажется, есть какой-то волшебный «барьер указателя», который нужно замаскировать

Цитата из C2:

Трехзвездочный Программист

Система рейтинга для C-программистов. Чем более косвенными являются ваши указатели (т.е. чем больше «*» перед вашими переменными), тем выше будет ваша репутация. Си-программистов без звезд практически не существует, так как практически все нетривиальные программы требуют использования указателей. Большинство из них - программисты одной звезды. В старые времена (ну, я молодой, так что, по крайней мере, для меня это похоже на старые времена), иногда можно было бы найти кусок кода, сделанный трехзвездным программистом, и дрожать от страха. Некоторые люди даже утверждали, что видели трехзвездочный код с задействованными указателями на более чем один уровень косвенности. Для меня это звучало так же реально, как НЛО.


2
github.com/psi4/psi4public/blob/master/src/lib/libdpd/… и тому подобное было написано 4-звездочным программистом. Он также мой друг, и, если вы достаточно прочтете код, вы поймете причину, по которой он достоин 4 звезд.
Джефф

13

Обратите внимание, что здесь есть два возможных вопроса: сколько уровней косвенности указателей мы можем достичь в типе C, и сколько уровней косвенности указателей мы можем поместить в один декларатор.

Стандарт C позволяет наложить максимум на первый (и дает минимальное значение для этого). Но это можно обойти с помощью нескольких объявлений typedef:

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

В конечном счете, это проблема реализации, связанная с представлением о том, насколько большой / сложной может быть C-программа, прежде чем она будет отклонена, что очень сильно зависит от компилятора.


4

Я хотел бы отметить, что создание типа с произвольным числом * - это то, что может произойти с метапрограммированием шаблона. Я забыл, что именно делал, но мне было предложено создать новые отдельные типы, имеющие мета-маневрирование между ними, используя рекурсивные типы T *.

Шаблон метапрограммирования - это медленное погружение в безумие, поэтому нет необходимости оправдываться при генерации типа с несколькими тысячами уровней косвенности. Это просто удобный способ отобразить целые числа peano, например, на расширение шаблона в качестве функционального языка.


Я признаю, что не совсем понимаю ваш ответ, но это дало мне новую область для изучения. :)
ankush981

3

Правило 17.5 стандарта MISRA C 2004 года запрещает более 2 уровней косвенного обращения к указателю.


15
Я уверен, что это рекомендация для программистов, а не для компиляторов.
Коул Джонсон

3
Я прочитал документ с правилом 17.5 о более чем 2 уровнях косвенного обращения к указателю. И это не обязательно запрещает более 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.это руководящие принципы, установленные этой организацией, а не правила, установленные языковым стандартом.
Фрэнсис Куглер

1

Нет такой вещи, как реальный лимит, но существует лимит. Все указатели являются переменными, которые обычно хранятся в стеке, а не в куче . Стек, как правило, небольшой (можно изменить его размер во время некоторых ссылок). Допустим, у вас есть стек 4 МБ, что вполне нормально. И скажем, у нас есть указатель размером 4 байта (размеры указателя не совпадают в зависимости от архитектуры, назначения и настроек компилятора).

В этом случае 4 MB / 4 b = 1024максимально возможное максимальное число будет 1048576, но мы не должны игнорировать тот факт, что некоторые другие вещи находятся в стеке.

Однако некоторые компиляторы могут иметь максимальное количество цепочек указателей, но пределом является размер стека. Так что если вы увеличиваете размер стека во время соединения с бесконечностью и имеете машину с бесконечной памятью, на которой работает ОС, которая обрабатывает эту память, то у вас будет неограниченная цепочка указателей.

Если вы используете int *ptr = new int;указатель и помещаете его в кучу, то это не совсем обычный способ ограничения размера кучи, а не стека.

РЕДАКТИРОВАТЬ Просто осознайте это infinity / 2 = infinity. Если у машины больше памяти, размер указателя увеличивается. Так что, если память бесконечна, а размер указателя бесконечен, значит, это плохие новости ... :)


4
A) Указатели могут храниться в куче ( new int*). B) An int*и an int**********имеют одинаковый размер, по крайней мере, на разумных архитектурах.

@rightfold A) Да, указатели могут храниться в куче. Но это было бы совсем не так, как создание контейнера, содержащего указатели, указывающие на следующий предыдущий указатель. Б) Конечно, int*и int**********имеют одинаковый размер, я не говорил, что они разные.
ST3

2
Тогда я не понимаю, насколько размер стека хоть как-то важен.

@rightfold Я думал об обычном способе распределения данных, когда все данные находятся в куче, а в стеке это всего лишь указатели на эти данные. Это было бы обычным способом, но я согласен, что в стек можно ставить указатели.
ST3

«Конечно, int * и int ********** имеют одинаковый размер» - стандарт не гарантирует этого (хотя я не знаю ни одной платформы, где бы это было не так).
Мартин Боннер поддерживает Монику

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.

КСТАТИ. Он использует 92Kоперативную память, поэтому просто представьте, как глубоко вы можете зайти.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.