Сколько уровней указателей у нас есть?

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

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

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

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

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

и т.д.

Например,

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

Ответ 1

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

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

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

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

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

Ответ 2

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

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

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

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

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

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

Ответ 3

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

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

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

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

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

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

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

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

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

Ответ 4

Как говорили люди, никакого предела "теоретически". Однако из интереса я побежал с 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;
}

Ответ 5

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

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

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

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

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

Ответ 6

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

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

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

#include <cstdio>

typedef void (*FuncType)();

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

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

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

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

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

Ответ 8

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

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

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

int **q = &p;

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

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

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

разрешено.

Ответ 9

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

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

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

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

Ответ 10

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

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

Цитата из C2:

     
  

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

         
    

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

    

Ответ 11

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

Ответ 12

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

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

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

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

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

Ответ 13

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

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

Ответ 14

Это зависит от того, где вы храните указатели. Если они находятся в стеке, у вас довольно низкий предел. Если вы храните его в куче, вы ограничиваете намного больше.

Посмотрите на эту программу:

#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 оперативной памяти, поэтому представьте, насколько глубоко вы можете идти.