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

423

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

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

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

Аналогично мы можем иметь

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

и т.д.

Например,

int ****************zz;
  • 567
    Если это когда-нибудь станет для вас реальной проблемой, вы делаете что-то очень неправильное.
  • 3
    Технически, вероятно, нет предела (хотя это зависит от компилятора). Реальный предел - это ваша (и ваши коллеги) умственная способность отслеживать, что на самом деле представляет ваша переменная. Создание и поддержание читабельности кода, как правило, важнее, чем оптимизация низкого уровня. С этой точки зрения, в производственном коде уже тройное косвенное обращение (например, int*** ) в большинстве случаев не рекомендуется.
Показать ещё 22 комментария
Теги:
pointers

14 ответов

399

В стандарте C указан нижний предел:

5.2.4.1 Ограничения перевода

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

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

Верхний предел специфичен для реализации.

  • 117
    Стандарт C ++ «рекомендует», чтобы реализация поддерживала не менее 256. (Читаемость рекомендует, чтобы вы не превышали 2 или 3, и даже тогда: более одного должно быть исключительным.)
  • 22
    Это ограничение о том, сколько в одной декларации; это не накладывает верхнюю границу на то, как много косвенности вы можете достичь с помощью нескольких typedef .
Показать ещё 8 комментариев
145

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

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

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

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

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

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

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

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

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

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

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

Здесь ссылка:

C99 Standard 5.2.4.1 Ограничения перевода:

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

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

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

  • 16
    Направления не переполняют стеки!
  • 11
    Как стек соотносится с косвенным указателем?
Показать ещё 7 комментариев
73

Как говорили люди, никакого предела "теоретически". Однако из интереса я побежал с g++ 4.1.2, и он работал с размером до 20 000. Однако компиляция была довольно медленной, поэтому я не пробовал больше. Поэтому я бы предположил, что 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, удваивая число * пока не получил тот, который не прошел, и предыдущий, который прошел; затем я выполнил бинарный поиск по этому интервалу для первого, который не прошел. Весь тест занял меньше второй бежать.)
58

Звучит весело.

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

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

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

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

  • 4
    В самом деле, унарные операторы производят праворекурсивные операции, что означает, что синтаксический анализатор смещения-смещения переместит все * узлы в стек, прежде чем сможет сделать сокращение.
25

Здесь нет предела, проверьте здесь.

Ответ зависит от того, что вы подразумеваете под "уровнями указателей". Если вы имеете в виду "Сколько уровней косвенности вы можете иметь в одной декларации?" ответ "Не менее 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 */

Если вы имеете в виду "Сколько уровней указателя вы можете использовать до того, как программа будет трудно читать", это вопрос вкуса, но есть предел. Существует два уровня косвенности (указатель на указатель на что-то). Больше, чем это становится немного сложнее думать о легко; не делайте этого, если альтернатива не будет хуже.

Если вы имеете в виду "Сколько уровней указателя указателя вы можете иметь во время выполнения", то нет предела. Этот момент особенно важен для круговых списков, в которых каждый node указывает на следующий. Ваша программа может следовать указателям навсегда.

  • 7
    Это почти наверняка предел, так как компилятор должен отслеживать информацию в ограниченном объеме памяти. ( g++ прерывается с внутренней ошибкой на 98242 на моей машине. Я ожидаю, что фактический предел будет зависеть от машины и нагрузки. Я также не ожидаю, что это будет проблемой в реальном коде.)
  • 2
    Да, @MatthieuM. Я только что обдумал теоретически :) Спасибо Джеймсу за завершение ответа
Показать ещё 5 комментариев
21

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

#include <cstdio>

typedef void (*FuncType)();

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

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

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

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

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

17

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

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

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

int **q = &p;

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

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

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

разрешено.

13

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

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

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

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

9

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

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

Цитата из C2:

     
  

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

         
    

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

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

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

  • 15
    Я уверен, что это рекомендация для программистов, а не для компиляторов.
  • 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. Это руководящие принципы, установленные этой организацией, в отличие от правил, установленных языковым стандартом.
1

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

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

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

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

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

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

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

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

  • 4
    A) Указатели могут храниться в куче ( new int* ). B) int* и int********** имеют одинаковый размер, по крайней мере, на разумных архитектурах.
  • 0
    @rightfold A) Да, указатели могут храниться в куче. Но это было бы совсем не так, как создание контейнера, который содержит указатели того, что указывает на следующий предыдущий указатель. Б) Конечно, int* и int********** имеют одинаковый размер, я не говорил, что они имеют разные.
Показать ещё 4 комментария
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. Он использует 92K оперативной памяти, поэтому представьте, насколько глубоко вы можете идти.

Ещё вопросы

Сообщество Overcoder
Наверх
Меню