Скільки рівнів покажчиків ми можемо мати?


443

Скільки покажчиків ( *) дозволено в одній змінній?

Розглянемо наступний приклад.

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 рівнів непрямості.
Стів314,

Відповіді:


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 та / або

155

Насправді, програми C зазвичай використовують нескінченне непряме вказівка. Один або два статичних рівня є загальними. Потрійна непрямість спостерігається рідко. Але нескінченність дуже поширене.

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

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

тепер ви можете мати list->next->next->next->...->next. Це дійсно просто кілька indirections покажчик: *(*(..(*(*(*list).next).next).next...).next).next. І в .nextосновному це вузол, коли він є першим членом структури, тому ми можемо уявити це як таке ***..***ptr.

Насправді обмежень у цьому немає, тому що посилання можуть бути прокладені циклом, а не гігантським виразом, як це, і більше того, структуру можна легко зробити круговою.

Таким чином, іншими словами, пов'язані списки можуть бути найкращим прикладом додавання іншого рівня непрямості для вирішення проблеми, оскільки ви робите це динамічно з кожною операцією натискання. :)


48
Це зовсім інше питання - структура, що містить вказівник на іншу структуру, дуже відрізняється від покажчика-покажчика. Intt ***** - це відмінний тип від int ****.
пухнастий

12
Це не дуже "інакше". Різниця - пухнаста. Він ближчий до синтаксису, ніж семантика. Вказівник на об’єкт вказівника чи вказівник на об’єкт структури, який містить вказівник? Це та сама річ. Перехід до десятого елемента списку - це десять рівнів адреси непрямості. (Звичайно ж , можливість висловити нескінченну структуру , залежить від типу структури будучи в стані точки до себе з допомогою неповного типу структури , так що list->nextі list->next->nextтого ж типу, в іншому випадку ми мали б побудувати нескінченний тип.)
Kaz

34
Я не усвідомлено помітив, що ваше ім’я пухнасте, коли я вживав слово "пухнастий". Субкосмічний вплив? Але я впевнений, що раніше вживав це слово таким чином.
Каз

3
Також пам’ятайте, що машинною мовою ви можете просто повторити щось на зразок LOAD R1, [R1], поки R1 є дійсним покажчиком на кожному кроці. Немає інших типів, крім "слова, яке містить адресу". Наявність оголошених типів чи ні, не визначає опосередкованість та кількість рівнів.
Каз

4
Не, якщо структура кругла. Якщо R1має місце розташування, яке вказує на себе, то воно LOAD R1, [R1]може бути виконане у нескінченному циклі.
Каз

83

Теоретично:

Ви можете мати стільки рівнів непрямостей, скільки вам захочеться.

Практично:

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

Ось посилання:

Стандарт C99 5.2.4.1 Ліміти перекладу:

- 12 декакторів покажчиків, масивів та функцій (у будь-яких комбінаціях), що змінюють арифметичний, структуровий, об'єднаний чи недійсний тип декларації.

Це визначає нижню межу, яку повинна підтримувати кожна реалізація . Зауважимо, що у футеноті стандарт додатково говорить:

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


16
непрямі не переповнюють жодних стеків!
Базиль Старинкевич

1
Виправлено, у мене виникло помилкове відчуття читання та відповіді на q як обмеження параметрів, що передаються функції. Я не знаю чому ?!
Алок Зберегти

2
@basile - Я очікую, що глибина стека буде проблемою в аналізаторі. Багато алгоритмів формального аналізу мають стек як ключовий компонент. Більшість компіляторів C ++, ймовірно, використовують варіант рекурсивного спуску, але навіть це покладається на стек процесора (або, педантично, на мову, яка діє так, ніби був стек процесора). Більше гніздування граматичних правил означає глибший стек.
Стів314,

8
непрямі не переповнюють жодних стеків! -> Ні! стек парсера може переповнюватися. Як стек відноситься до непрямості вказівника? Парсер стек!
Pavan Manjunath

Якщо *перевантажена для декількох класів підряд, і кожна перевантаження повертає об'єкт іншого типу в рядку, то для таких ланцюжкових викликів функцій може бути потік stackoverflow.
Наваз

76

Як казали люди, немає обмежень "в теорії". Однак із інтересу я запустив це з g ++ 4.1.2, і він працював з розміром до 20 000. Хоча компіляція була досить повільною, тому я не пробував вище. Тому я здогадуюсь, що і г ++ не встановлює жодних обмежень. (Спробуйте встановити 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), 100k + *без збоїв! Я думаю, що тут є межами обладнання.

(перевірено лише змінною декларацією)


5
Дійсно, виробництво для одинарних операторів є праворекурсивним, а це означає, що аналізатор зменшення зсуву змістить усі *вузли на стек, перш ніж вдасться зробити скорочення.
Каз

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 на моїй машині. Очікую, що фактична межа буде залежати від машини та навантаження. Я також не очікую, що це буде проблемою в реальному коді.)
Джеймс Канзе

2
Так @MatthieuM. : Я просто розглядав теоретично :) Дякую Джеймсу за завершену відповідь
Nandkumar Tekale

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)();
  /* ... */
}

Як показано тут, це дає:

Привіт Світ!
Привіт Світ!
Привіт Світ!

І це не передбачає жодних накладних витрат, тому ви, ймовірно, можете складати їх стільки, скільки захочете ..., поки ваш компілятор не задихається у файлі.


20

Межі немає . Вказівник - це шматок пам'яті, вміст якого - адреса.
Як ти сказав

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

Вказівник на покажчик - це також змінна, яка містить адресу іншого вказівника.

int **q = &p;

Тут qвказівник на покажчик, що містить адресуp якому є якої вже містить a.

У покажчику на покажчик немає нічого особливого.
Тож немає обмеження для ланцюжка понітей, які містять адресу іншого вказівника.
тобто.

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

дозволено.


17

Кожен розробник C ++ повинен був почути про (в) відомого програму "Три зірки"

І справді, здається, існує якийсь магічний "вказівний бар'єр", який доводиться маскувати

Цитата від C2:

Тризірковий програміст

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


2
github.com/psi4/psi4public/blob/master/src/lib/libdpd/… та інше написав 4-зірковий програміст. Він також мій друг, і якщо ви досить прочитаєте код, зрозумієте причину, чому він гідний 4 зірок.
Джефф

13

Зауважте, що тут є два можливі питання: скільки рівнів непрямості вказівника ми можемо досягти в типі С і скільки рівнів непрямості вказівника ми можемо скласти в один декларатор.

Стандарт C дозволяє накласти максимум на колишній (і дає мінімальне значення для цього). Але це можна обійти за допомогою декількох декларацій typedef:

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

Отже, в кінцевому рахунку, це питання реалізації, пов’язаний з ідеєю того, наскільки великою / складною може бути складена програма С до її відхилення, що дуже специфічно для компілятора.


4

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

Метапрограмування шаблонів - це повільний спуск у божевілля, тому не потрібно виправдовуватися під час генерування типу з кількома тисячами рівня непрямості. Це просто зручний спосіб відображення цілих чисел піано, наприклад, на розширенні шаблонів як функціональної мови.


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

3

Правило 17.5 стандарту MISRA C 2004 року забороняє більше 2 рівнів непрямості вказівників.


15
Досить впевнений, що це рекомендація для програмістів, а не для компіляторів.
Коул Джонсон

3
Я читав документ із правилом 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

Не існує такого поняття, як реальна межа, але межа існує. Усі покажчики - це змінні, які зазвичай зберігаються в стеці, а не в купі . Стек зазвичай невеликий (можна змінити його розмір під час деяких зв'язків). Тож скажемо, у вас є стек 4 Мб, що є цілком нормальним розміром. І скажемо, що у нас є вказівник розміром 4 байти (розміри вказівника не однакові залежно від архітектури, налаштувань цілі та компілятора).

У цьому випадку 4 MB / 4 b = 1024можлива максимальна кількість буде 1048576, але ми не повинні ігнорувати той факт, що деякі інші речі знаходяться в стеці.

Однак деякі компілятори можуть мати максимальну кількість ланцюгів вказівників, але обмеженням є розмір стека. Отже, якщо ви збільшите розмір стека під час з'єднання з нескінченністю і матимете машину з нескінченною пам'яттю, яка працює з ОС, яка обробляє цю пам'ять, тому у вас буде необмежений ланцюжок покажчиків.

Якщо ви використовуєте int *ptr = new int;і поміщаєте вказівник у купу, то не таким звичайним обмеженням шляху буде розмір купи, а не стек.

EDIT Просто зрозумійте це 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;
}

Він створює покажчики 1М і показує, що вказує на те, що легко помітити, що ланцюг йде до першої змінної number.

До речі. Він використовує 92Kоперативну пам'ять, тому просто уявіть, наскільки глибоко ви можете пройти.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.