Що саме є покажчиком C, якщо не адресою пам'яті?


206

У поважному джерелі про C після обговорення &оператора наводиться така інформація :

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

Інші матеріали, які я прочитав (з однаково авторитетних джерел, я б сказав), завжди беззаперечно посилалися на покажчики та &оператор як дані пам'яті. Я б хотів продовжувати шукати актуальність справи, але це важко, коли авторитетні джерела НАПРЯМИ НЕ погоджуються.

Зараз я трохи розгублений - що саме таке вказівник, якщо не адреса пам'яті?

PS

Пізніше автор каже: ... я продовжуватиму використовувати термін "адреса", хоча придумати інший [термін] було б ще гірше.


118
Вказівник - це змінна, яка містить адресу. У нього також є своя адреса. Це принципова відмінність між вказівником та масивом. Масив ефективно - це адреса (і, як наслідок, сама її адреса ).
WhozCraig

7
Яке ваше "поважне джерело" для цитати?
Cornstalks

22
Кінцевим поважним джерелом є стандарт мови, а не книги, напіввиведені з нього, і напівпритягнуті з-за автора слова. Я навчився цього важким шляхом, роблячи майже кожну помилку, яку я міг, і повільно будуючи ментальну модель С трохи ближче до описаної стандартом, а потім, нарешті, замінивши цю модель на модель стандарту.
Олексій Фрунзе

9
@thang Люди думають, що вказівник = ціле, тому що це часто так (x86 Linux і Windows "нас цього вчать"), тому що люди люблять узагальнювати, тому що люди не знають добре мовного стандарту і тому, що у них мало досвіду з радикально різними платформи. Ці ж люди, ймовірно, припускають, що вказівник даних і вказівник на функцію можуть бути перетворені один на одного і дані можуть бути виконані у вигляді коду та коду, доступ до якого є даними. Хоча це може бути правдою для архітектур фон Неймана (з 1 адресним простором), але не обов'язково і для гарвардських архітектур (без коду та простору даних).
Олексій Фрунзе

6
Стандарти @exebook не призначені для новачків (особливо, повних). Вони не повинні наводити лагідні знайомства та безліч прикладів. Вони формально щось визначають, тому професіонали це можуть правильно реалізувати.
Олексій Фрунзе

Відповіді:


148

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

Значення вказівника може бути якимось ідентифікатором або ручкою або комбінацією декількох ідентифікаторів (привітатись із сегментами та зсувами x86), а не обов’язково реальною адресою пам'яті. Цей ідентифікатор може бути будь-яким, навіть текстовим рядком фіксованого розміру. Представлення без адреси може бути особливо корисним для перекладача С.


34
Не можна багато пояснювати. Кожна змінна має свою адресу в пам'яті. Але не потрібно зберігати їх адреси в покажчиках на них. Натомість ви можете нумерувати свої змінні від 1 до будь-яких і зберігати це число в покажчику. Це абсолютно законно для мовного стандарту, якщо впровадження знає, як перетворити ці числа на адреси та як робити арифметику вказівника з цими числами та всіма іншими речами, необхідними стандартом.
Олексій Фрунзе

4
Я хотів би додати, що на x86 адреса пам’яті складається з селектора селектора та зміщення, таким чином представляючи вказівник як сегмент: offset все ще використовує адресу пам’яті.
thang

6
@Lundin У мене немає проблем із ігноруванням загальної природи стандарту та неприйнятного, коли я знаю свою платформу та свій компілятор. Однак оригінальне запитання є загальним, тому ви не можете ігнорувати стандарт, відповідаючи на нього.
Олексій Фрунзе

8
@Lundin Вам не потрібно бути революціонером чи вченим. Припустимо, ви хочете емулювати 32-бітну машину на фізичній 16-бітній машині, і ви розширите 64 КБ оперативної пам’яті до 4 Гб, використовуючи дискове сховище та впроваджуючи 32-бітні покажчики як компенсації у величезний файл. Ці вказівники не є реальною адресою пам'яті.
Олексій Фрунзе

6
Найкращим прикладом, який я коли-небудь бачив, було реалізація C для Symbolics Lisp Machines (близько 1990 року). Кожен об’єкт C був реалізований у вигляді масиву Lisp, а покажчики були реалізовані у вигляді пари масиву та індексу. Через перевірку меж масиву Lisp ви ніколи не можете переходити з одного об'єкта на інший.
Вармар

62

Я не впевнений у своєму джерелі, але тип мови, який ви описуєте, походить із стандарту C:

6.5.3.2 Оператори адреси та опосередкування
[...]
3. Онар і оператор вказують адресу свого операнда. [...]

Отже ... так, вказівники вказують на адреси пам'яті. Принаймні, саме так пропонує стандарт C.

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

Я можу зберігати адресу "42 Wallaby Way, Sydney" у змінній (і ця змінна була б "вказівником" свого роду, але оскільки це не адреса пам'яті, це не те, що ми би правильно називали "покажчиком"). Ваш комп’ютер має адреси для своїх відра пам'яті. Покажчики зберігають значення адреси (тобто вказівник зберігає значення "42 Way Wallaby, Sydney", що є адресою).

Редагувати: Я хочу розкрити коментар Олексія Фрунзе.

Що саме є вказівником? Давайте розглянемо стандарт С:

6.2.5 Типи
[...]
20. [...] тип покажчика може бути отриманий з типу функції або типу об'єкта, званого посилання типу . Тип вказівника описує об'єкт, значення якого надає посилання на сутність типу посилання. Тип вказівника, похідний від посиланого типу T, іноді називається "вказівником на T". Побудова типу вказівника з посилального типу називається '' виведенням типу вказівника ''. Тип вказівника - це повний тип об'єкта.

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

6.3.2.3 Покажчики
[...]
5. Ціле число може бути перетворене на будь-який тип вказівника. За винятком випадків, зазначених раніше, результат визначений реалізацією, може бути неправильно вирівняний, не може вказувати на сутність посиланого типу та може бути представленням пастки.

Вищенаведена цитата говорить про те, що ми можемо перетворити ціле число в покажчик. Якщо ми це зробимо (тобто, якщо ми введемо ціле значення в покажчик замість конкретного посилання на об'єкт або функцію), то вказівник "може не вказувати на сутність типу посилання" (тобто він може не надати посилання на об'єкт або функцію). Це може надати нам щось інше. І це одне місце, де ви можете вписати якусь ручку чи ідентифікатор у вказівник (тобто вказівник не вказує на об’єкт; він зберігає значення, яке щось представляє, але це значення може бути не адресою).

Так, так, як каже Олексій Фрунзе, можливо, вказівник не зберігає адресу до об'єкта чи функції. Можливо, що вказівник замість цього зберігає якусь "ручку" або ID, і ви можете це зробити, призначивши вказівник якесь довільне ціле значення. Що ця ручка або ID представляє, залежить від системи / середовища / контексту. Поки ваша система / реалізація може мати сенс значення, ви знаходитесь у хорошій формі (але це залежить від конкретного значення та конкретної системи / реалізації).

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

Це закінчилося довше, ніж я думав, що це буде ...


3
У інтерпретаторі C покажчик може містити ідентифікатор без адреси / ручку / тощо.
Олексій Фрунзе

4
@exebook Стандарт ні в якому разі не обмежується складеним C.
Олексій Фрунзе

7
@Lundin Bravo! Давайте ще більше ігноруємо стандарт! Наче ми його вже недостатньо ігнорували і через це не виробляли баггі та погано портативні програми. Також, будь ласка, не зазначайте, що оригінальне запитання є загальним і як таке потребує загальної відповіді.
Олексій Фрунзе

3
Коли інші кажуть, що вказівник може бути ручкою чи іншим, крім адреси, це не означає, що ви можете примусити дані до вказівника, вкинувши ціле число в покажчик. Вони означають, що компілятор може використовувати щось інше, ніж адреси пам'яті для реалізації покажчиків. У процесорі Alpha з ABI DEC вказівник функції не був адресою функції, а був адресою дескриптора функції, а дескриптор містив адресу функції та деякі дані про параметри функції. Справа в тому, що стандарт C дуже гнучкий.
Eric Postpischil

5
@Lundin: Твердження, що покажчики реалізуються як цілі адреси на 100% існуючих комп'ютерних систем у реальному світі, є помилковим. Комп'ютери існують з адресоюванням слів та адресованою компенсацією за сегментами. Компілятори все ще існують із підтримкою вказівників на близькі та далекі. Існують комп'ютери PDP-11, з RSX-11 та Builder завдань та його накладками, в яких вказівник повинен ідентифікувати інформацію, необхідну для завантаження функції з диска. Вказівник не може мати адресу пам'яті об'єкта, якщо об'єкт не знаходиться в пам'яті!
Eric Postpischil

39

Вказівник проти змінної

На цьому малюнку

pointer_p - це вказівник, який знаходиться на 0x12345, і вказує на змінну змінної_v на 0x34567.


16
Це не тільки не стосується поняття адреси на відміну від вказівника, але й цілісно пропускає точку, що адреса не є просто цілим числом.
Жил "ТАК - перестань бути злим"

19
-1, це просто пояснює, що таке покажчик. Це був не question-- і ви відтісняючи всі складнощі , що питання є о.
alexis

34

Думати про вказівник як адресу - це наближення . Як і всі наближення, це досить добре, щоб бути корисним іноді, але це також не точно, що означає, що покладатися на нього викликає неприємності.

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

  • адреса об'єкта, який може бути разименовано (якщо pмістить адресу , xто вираз *pмає таке ж значення , як x);
  • покажчик NULL , з яких NULLв якості прикладу;
  • недійсний вміст, який не вказує на об’єкт (якщо pвін не містить дійсного значення, тоді *pможе зробити що-небудь («невизначена поведінка»), при збої програми досить поширена можливість).

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

Зокрема, вказівник має тип. На більшості платформ тип покажчика не має впливу під час виконання, але він має вплив, що виходить за рамки типу під час компіляції. Якщо pвказівник на int( int *p;), то p + 1вказує на ціле число, яке є sizeof(int)байтами після p(припустимо, що p + 1це все-таки дійсний покажчик). Якщо qвказівник на charце вказує на ту саму адресу, що і p( char *q = p;), то q + 1це не та сама адреса, як p + 1. Якщо ви вважаєте, що вказівник є адресами, не надто інтуїтивно зрозуміло, що наступна адреса відрізняється для різних покажчиків на одне місце.

У деяких середовищах можливо мати декілька значень вказівників з різними поданнями (різні схеми бітів в пам'яті), які вказують на одне місце в пам'яті. Ви можете думати про це як різні вказівники, що містять одну і ту ж адресу, або як різні адреси для одного і того ж місця - метафора в цьому випадку не зрозуміла. ==Оператор завжди говорить вам чи два операнда вказують на те ж місце, так що на цих умовах ви можете мати , p == qхоча pі qмають різні бітові шаблони.

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

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

Загалом, думати покажчики як адреси не так вже й погано, якщо ви це пам’ятаєте

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

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

Спочатку є деякі відомі обмеження; наприклад, ціле число, яке позначає місце за межами адресного простору вашої програми, не може бути дійсним вказівником. Несогласна адреса не робить дійсним покажчиком для типу даних, який вимагає вирівнювання; наприклад, на платформі, де intпотрібно 4-байтове вирівнювання, 0x7654321 не може бути дійсним int*значенням.

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

unsigned int x = 0;
unsigned short *p = (unsigned short*)&x;
p[0] = 1;
printf("%u = %u\n", x, *p);

Ви можете очікувати, що на машині, що працює на заводі, де sizeof(int)==4і sizeof(short)==2, це або друкується 1 = 1?(мало-ендіанський), або 65536 = 1?(великий-ендіанський). Але на моєму 64-розрядному ПК з ОС Linux GCC 4.4:

$ c99 -O2 -Wall a.c && ./a.out 
a.c: In function main’:
a.c:6: warning: dereferencing pointer p does break strict-aliasing rules
a.c:5: note: initialized from here
0 = 1?

GCC досить люб'язний, щоб попередити нас про те, що відбувається в цьому простому прикладі - у більш складних прикладах компілятор може не помітити. Оскільки pмає інший тип &x, зміна pпунктів на те, на що не може вплинути &x(за межами деяких чітко визначених винятків). Тому компілятор може вільно зберігати значення xв регістрі та не оновлювати цей регістр як *pзміни. Програма відміняє два вказівники на одну і ту ж адресу і отримує два різних значення!

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

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


5
+1. Інші відповіді, здається, пропускають, що вказівник надходить з інформацією про тип. Це набагато важливіше, ніж адреса / ідентифікатор / будь-яке обговорення.
undur_gongor

+1 Відмінні бали щодо інформації про тип. Я не впевнений, що приклади компілятора є правильними, наприклад ... Це здається дуже малоймовірним, наприклад, що *p = 3гарантовано досягти успіху, коли p не буде ініціалізовано.
LarsH

@LarsH Ви маєте рацію, дякую, як я це написав? Я замінив це прикладом, який навіть демонструє дивну поведінку на моєму ПК.
Жил "ТАК - перестань бути злим"

1
гм, NULL - це ((недійсне *) 0) ..?
Анікет Інге

1
@ gnasher729 Нульовий покажчик - це вказівник. NULLні, але для рівня деталізації, необхідного тут, це нерелевантне відволікання. Навіть для щоденного програмування той факт, що NULLможе бути реалізований як щось, що не говорить "покажчик", не виникає часто (в першу чергу переходить NULLдо варіативної функції - але навіть там, якщо ви її не кидаєте , ви вже робите припущення, що всі типи вказівників мають однакове представлення).
Жиль "ТАК - перестань бути злим"

19

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

Наприклад:

int q = 10; /*say q is at address 0x10203040*/
int *p = &q; /*means let p contain the address of q, which is 0x10203040*/
*p = 20; /*set whatever is at the address pointed by "p" as 20*/

Це воно. Це так просто.

введіть тут опис зображення

Програма для демонстрації того, що я говорю, та її результатів є тут:

http://ideone.com/rcSUsb

Програма:

#include <stdio.h>

int main(int argc, char *argv[])
{
  /* POINTER AS AN ADDRESS */
  int q = 10;
  int *p = &q;

  printf("address of q is %p\n", (void *)&q);
  printf("p contains %p\n", (void *)p);

  p = NULL;
  printf("NULL p now contains %p\n", (void *)p);
  return 0;
}

5
Це може сплутати ще більше. Аліса, бачиш кота? Ні, я не бачу лише посмішки кота. Отже, кажучи, що вказівник - це адреса, або вказівник - це змінна, яка містить адресу, або кажучи, що вказівник - це назва поняття, яке посилається на ідею адреси, наскільки далеко письменники можуть піти в заплутанні ближніх?
exebook

@exebook для виправлених у покажчиках, це досить просто. Може, допоможе малюнок?
Анікет Інге

5
Вказівник не обов'язково містить адресу. У інтерпретаторі C це може бути щось інше, якась ідентифікація / ручка.
Олексій Фрунзе

"Label" або ім'я змінної є компілятором / асемблером і не існує на рівні машини, тому я не думаю, що він повинен з'являтися в пам'яті.
Бен

1
@Aniket Змінна вказівника може містити значення вказівника. Вам потрібно зберігати результат fopenзмінної лише у тому випадку, якщо вам потрібно використовувати її не один раз (що fopen, як правило, майже весь час).
Жил "ТАК - перестань бути злим"

16

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

Судячи з усіх написаних відповідей, деякі люди припускають, що (1) адреса повинна бути цілим числом, а (2) вказівник не повинен бути віртуальним, щоб цього не було сказано в специфікації. З урахуванням цих припущень, то чітко вказівники не обов'язково містять адреси.

Однак ми бачимо, що хоча (2), мабуть, правда, (1), мабуть, не повинно бути правдою. І що робити з тим, що & називається адресою оператора відповідно до відповіді @ CornStalks? Чи означає це, що автори специфікації мають намір вказівник містити адресу?

Тож чи можна сказати, що вказівник містить адресу, але адреса не повинна бути цілою? Може бути.

Я думаю, що все це - велетенська семантична розмова. Це абсолютно нікчемно практично. Чи можете ви придумати компілятор, який генерує код таким чином, що значення вказівника не є адресою? Якщо так, то що? Це те, що я думав...

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

Наприклад,

 int x;
 int* y = &x;
 char* z = &x;

і y і z є вказівниками, але y + 1 і z + 1 різні. якщо вони є адресами пам'яті, чи не могли б ці вирази дати те саме значення?

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

55555, мабуть, не вказівник, хоча це може бути адреса, але (int *) 55555 - це вказівник. 55555 + 1 = 55556, але (int *) 55555 + 1 є 55559 (+/- різниця у розміріof (int)).


1
+1 для вказівки арифметики вказівника не те саме, що арифметична за адресами.
kutschkem

У випадку 16-бітного 8086 адреса пам'яті описується базовим сегментом + зміщенням, обидва 16 біт. Існує багато комбінацій бази сегмента + зміщення, які дають однакову адресу в пам'яті. Цей farпокажчик не просто "ціле число".
vonbrand

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

@tang, ідея "вказівник == адреса" неправильна . Те, що всі та їх улюблена тітка продовжують говорити, не робить це правильним.
vonbrand

@vonbrand, а чому ти зробив цей коментар під моїм дописом? Я не сказав, що це правильно чи неправильно. Насправді це правильно в певних сценаріях / припущеннях, але не завжди. Дозвольте ще раз підсумувати пункт поста (вдруге). вся моя відповідь у відповіді полягає в тому, що це не актуально. це все просто педантично, і головне питання не стосується інших відповідей. було б доцільніше прокоментувати відповіді, які роблять претензію, що вказівник == адреса або адреса == ціле число. дивіться мої коментарі під постом Олексія стосовно сегмента: offset.
thang

15

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

Найбільш вірогідним джерелом горя, безумовно, є арифметика вказівника, яка насправді є однією із сильних сторін С. Якщо вказівник був адресою, ви очікуєте, що арифметика вказівника буде арифметичною адресою; але це не так. Наприклад, додавання 10 до адреси має дати вам адресу, яка більша на 10 одиниць адресації; але додаючи 10 до кроку вказівника, він збільшує в 10 разів більше розміру об’єкта, на який він вказує (і навіть не на фактичний розмір, а округляється до межі вирівнювання). При int *звичайній архітектурі з 32-бітовими цілими числами додавання до неї 10 збільшить її на 40 одиниць адресації (байтів). Досвідчені програмісти програми C знають про це і живуть з цим, але ваш автор, очевидно, не є шанувальником неохайних метафор.

Є додатковий питання про те, як вміст вказівника представляє місце в пам'яті: Як пояснено в багатьох відповідях, адреса не завжди є цілою (або довгою). У деяких архітектурах адреса - це "сегмент" плюс зміщення. Вказівник навіть може містити лише зміщення в поточний сегмент ("біля" вказівника), який сам по собі не є унікальною адресою пам'яті. І вміст вказівника може мати лише непряме відношення до адреси пам'яті, оскільки апаратне забезпечення це розуміє. Але автор цитованої цитати навіть не згадує репрезентацію, тому я думаю, що це було на увазі концептуальною еквівалентністю, а не репрезентацією.


12

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

Наприклад, наведено:

union {
    int i;
    char c;
} u;

Ви можете мати три різні вказівники, які вказують на цей самий об’єкт:

void *v = &u;
int *i = &u.i;
char *c = &u.c;

Якщо порівнювати значення цих покажчиків, всі вони рівні:

v==i && i==c

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

i++;
c++;
// You can't perform arithmetic on a void pointer, so no v++
i != c

Змінні iта cбудуть мати різні значення в цей момент, тому що i++викликає, iщо містить адресу наступного доступного цілого числа, і c++викликає cвказівку на наступний адресний символ. Зазвичай цілі числа займають більше пам’яті, ніж символи, тому iв кінцевому підсумку cвони будуть мати більшу величину, ніж після того, як вони обидва збільшені.


2
+1 Дякую Завдяки покажчикам цінність і тип настільки ж невіддільні, як можна відокремити тіло людини від своєї душі.
Акі Суйконен

i == cнеправильно сформована (ви можете порівнювати покажчики на різні типи лише в тому випадку, коли є неявна конверсія від одного до іншого). Крім того, виправити це за допомогою символу керування означає, що ви застосували конверсію, і тоді дискусійно, чи змінює конверсія значення чи ні. (Ви можете стверджувати, що це не так, але тоді це просто твердження того самого, що ви намагалися довести цим прикладом).
ММ

8

Марк Бессі вже сказав це, але це потрібно ще раз підкреслити, поки не зрозуміємо.

Покажчик має стільки ж спільного з змінною, ніж з буквальною 3.

Покажчик - кортеж значення (адреси) та типу (з додатковими властивостями, наприклад, лише для читання). Тип (і додаткові параметри, якщо такі є) можуть додатково визначати або обмежувати контекст; напр. __far ptr, __near ptr: який контекст адреси: стек, купа, лінійна адреса, зміщення звідкись, фізична пам'ять чи що.

Це властивість типу робить арифметику вказівника дещо іншою за цілу арифметику.

Лічильних прикладів покажчика того, що не бути змінною, занадто багато, щоб їх ігнорувати

  • fopen, що повертає вказівник FILE. (де змінна)

  • покажчик стека або покажчик кадру, як правило, нереєстровані регістри

    *(int *)0x1231330 = 13; - закидання довільного цілого значення до типу pointer_of_integer та запис / читання цілого числа, не вводячи змінної

Протягом життя C-програми буде багато інших примірників тимчасових покажчиків, які не мають адреси - і тому вони не є змінними, а виразами / значеннями з типом, пов’язаним із компіляцією часу.


8

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

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

Але в наші дні для архітектури ПК та ARM з плоскою моделлю пам’яті та мовою С, що склалася на початковому етапі, добре думати, що вказівник є цілочисельною адресою на якесь місце в одновимірній адресній пам'яті.


ПК ... модель плоскої пам'яті? що таке селектори?
Танга

Прямий. І коли відбудеться наступна зміна архітектури, можливо, з окремими проміжками даних коду та adn, або хтось повернеться до поважної архітектури сегмента (що має багато сенсу для безпеки, може навіть додати якийсь ключ до номера сегмента + зміщення, щоб перевірити дозволи) прекрасні "покажчики - це лише цілі числа", вони руйнуються.
vonbrand

7

Вказівник, як і будь-яка інша змінна в С, по суті є сукупністю бітів, які можуть бути представлені одним або декількома об'єднаними unsigned charзначеннями (як і будь-який інший тип каріабельного, sizeof(some_variable)вказуватиме кількість unsigned charзначень). Те, що робить вказівник відмінним від інших змінних, полягає в тому, що компілятор C інтерпретує біти в покажчику як ідентифікаційне місце, де може зберігатися змінна. У C, на відміну від деяких інших мов, можна запитати простір для декількох змінних, а потім перетворити покажчик на будь-яке значення в цьому наборі в покажчик на будь-яку іншу змінну в межах цього набору.

Багато компіляторів реалізують покажчики за допомогою своїх бітів зберігають фактичні адреси машин, але це не єдина можлива реалізація. Реалізація може зберігати один масив - недоступний коду користувача - перелік апаратної адреси та розміщений розмір усіх об'єктів пам'яті (наборів змінних), якими користувалася програма, і кожен вказівник містить індекс у масив уздовж зі зміщенням від цього показника. Така конструкція дозволила б системі не тільки обмежувати код лише оперативною пам'яттю, якою вона володіє, але й гарантувати, що вказівник на один елемент пам'яті не може бути випадково перетворений у вказівник на інший елемент пам'яті (у системі, яка використовує апаратне забезпечення адреси, якщо fooі може замість цього вказувати на перший пунктbar є масивами з 10 елементів, які послідовно зберігаються в пам'яті, вказівник на "одинадцятий" елементfoobar, але в системі, де кожен "покажчик" є ідентифікатором об'єкта та зміщенням, система може потрапити в пастку, якщо код спробував проіндексувати покажчик за fooмежі призначеного діапазону). У такій системі також можна було б усунути проблеми з фрагментацією пам'яті, оскільки фізичні адреси, пов'язані з будь-якими вказівниками, можна переміщувати.

Зауважте, що хоча покажчики є дещо абстрактними, вони не зовсім абстрактні, щоб дозволити компілятору С повністю відповідати стандартам для впровадження сміттєзбірника. Компілятор C визначає, що кожна змінна, включаючи вказівники, представлена ​​у вигляді послідовності unsigned charзначень. З огляду на будь-яку змінну, можна розкласти її на послідовність чисел і пізніше перетворити цю послідовність чисел у змінну початкового типу. Отже, програмою можна було бcalloc деякого сховища (отримання вказівника на неї), зберігає щось там, розкладає покажчик на ряд байтів, виводить їх на екран, а потім видаляє всі посилання на них. Якщо програма потім прийняла деякі клавіатури з клавіатури, відновила їх до вказівника, а потім спробувала прочитати дані з цього вказівника, і якщо користувач ввів ті самі цифри, що програма відображалася раніше, програмі потрібно було б вивести дані які були збережені вcalloc пам'яті 'ed. Оскільки немає жодного можливого способу, яким комп'ютер міг би знати, чи зробив користувач копію відображених номерів, не було б можливим, щоб комп'ютер міг знати, чи може в майбутньому отримати доступ до вищезазначеної пам'яті.


На великих режимах, можливо, ви могли б виявити будь-яке використання значення вказівника, яке могло б "просочити" його числове значення, і закріпити розподіл так, щоб збирач сміття не збирав і не переміщав його (якщо free, звичайно, не називається явно). Чи буде корисна реалізація все корисною - інша справа, оскільки її здатність збирати може бути занадто обмеженою, але ви, принаймні, можете назвати це збирачем сміття :-) Призначення покажчика та арифметика не "просочують" значення, але будь-який доступ до char*невідомого походження мав би бути перевірений.
Стів Джессоп

@SteveJessop: Я думаю, що така конструкція була б гіршою, ніж марною, оскільки за кодом було б неможливо знати, які вказівники потрібно звільнити. Сміттєзбірники, які припускають, що схоже на вказівник, може бути надмірно консервативним, але в цілому речі, схожі на (але не є), покажчики мають можливість змінитись, таким чином уникаючи "постійних" витоків пам'яті. Здійснення будь-яких дій, схожих на розкладання вказівника на байти, назавжди заморозивши вказівник, є гарантованим рецептом витоків пам'яті.
supercat

Я думаю, що це все-таки не вдасться з міркувань продуктивності - якщо ви хочете, щоб ваш код запускався повільно, тому що кожен доступ перевіряється, тоді не пишіть його на C ;-) У мене більші сподівання на винахідливість програмістів C, ніж ви, оскільки я вважаю, що в той час як це незручно, це, мабуть, неправдоподібно уникати непотрібного закріплення виділень. У будь-якому випадку, C ++ визначає "безпечно виведені покажчики" саме для того, щоб вирішити цю проблему, тому ми знаємо, що робити, якщо ми коли-небудь захочемо збільшити абстрактність покажчиків C до рівня, коли вони підтримують досить ефективний збір сміття.
Стів Джессоп

@SteveJessop: Щоб система GC була корисною, вона повинна бути здатна надійно звільнити пам'ять, на яку freeне було викликано, або запобігти будь-якому посиланню на звільнений об'єкт стати посиланням на живий об'єкт [навіть при використанні ресурсів, які вимагають явне управління протягом усього життя, GC все ще може корисно виконувати останню функцію]; система GC, яка іноді помилково розглядає об'єкти як живі посилання на них, може бути корисною, якщо ймовірність того, що N об’єктів буде непотрібно закріплювати, одночасно наближається до нуля, оскільки N стає великим . Якщо хтось не бажає позначити помилку компілятора ...
supercat

... для коду, який є дійсним C ++, але для якого компілятор не зміг би довести, що покажчик ніколи не може бути перетворений у невпізнанну форму, я не бачу, як можна було б уникнути ризику, що програма, яка насправді ніколи використовує покажчики, оскільки цілі числа можуть бути помилково розцінені так.
supercat

6

Вказівник - це тип змінної, який є наявним в C / C ++ і містить адресу пам'яті. Як і будь-яка інша змінна, вона має власну адресу і займає пам'ять (обсяг залежить від платформи).

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


1
Як правило, це ручка / ідентифікатор. Зазвичай це звичайна адреса.
Олексій Фрунзе

Я підкоригував свою відповідь, щоб бути трохи більше ПК на визначення Handle у wikipedia. Мені подобається посилатися на покажчики як на конкретний екземпляр ручки, оскільки ручка може бути просто посиланням на покажчик.
Меттью Сандерс

6

КОРОТКИЙ РЕЗЮМЕ (який я також поставлю вгорі):

(0) Мислення покажчиків як адрес часто є хорошим інструментом навчання і часто є реальною реалізацією для покажчиків на звичайні типи даних.

(1) Але в багатьох, можливо більшості, вказівники компіляторів на функції не є адресами, а є більшими за адресу (як правило, 2х, іноді більше), або насправді вказівники на структуру в пам'яті, ніж містять адреси функцій та інше постійний басейн.

(2) Покажчики на члени даних та покажчики методів часто навіть незнайоміші.

(3) Спадковий код x86 з проблемами вказівника FAR та NEAR

(4) Кілька прикладів, особливо IBM AS / 400, із захищеними "жировими покажчиками".

Я впевнений, що ви можете знайти більше.

ДЕТАЛІ:

UMMPPHHH !!!!! Багато відповідей поки що є досить типовими відповідями "програміст віні", але не компілятор weenie або апаратне weenie. Оскільки я претендую на апаратне віні і часто працюю з компіляторними вихідними, дозвольте мені кинути свої два центи:

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

Чудово.

Але навіть на багатьох таких компіляторах певні вказівники НЕ є адресами. Ви можете це сказати, подивившись sizeof(ThePointer).

Наприклад, вказівники на функції іноді бувають набагато більшими, ніж звичайні адреси. Або вони можуть включати рівень непрямості. Ця статтянадає одне опис із процесором Intel Itanium, але я бачив інші. Зазвичай для виклику функції ви повинні знати не тільки адресу коду функції, а й адресу постійного пулу функції - область пам'яті, з якої константи завантажуються однією інструкцією щодо завантаження, а не компілятор, який повинен генерувати 64-бітова константа з декількох інструкцій Load Immediate, Shift та OR АБО. Отже, замість однієї 64-бітової адреси вам потрібно 2 64-бітні адреси. Деякі ABI (бінарні інтерфейси додатків) переміщують це як 128 біт, тоді як інші використовують рівень непрямості, при цьому вказівник функції фактично є адресою дескриптора функції, який містить лише 2 згадані фактичні адреси. Який краще? Залежить від вашої точки зору: продуктивність, розмір коду, і деякі проблеми сумісності - часто код передбачає, що покажчик може бути переданий на довгий або довгий, але може також припускати, що довгий довгий рівно 64 біт. Такий код може не відповідати стандартам, але, тим не менш, клієнти можуть захотіти, щоб він працював.

Багато хто з нас мають болісні спогади про стару сегментовану архітектуру Intel x86, з БЛИЗЬКИМИ Вказівниками та дальніми вказівниками. На щастя, вони вже майже вимерли, тому лише короткий підсумок: у 16-бітному реальному режимі фактична лінійна адреса була

LinearAddress = SegmentRegister[SegNum].base << 4 + Offset

Якщо в захищеному режимі це може бути

LinearAddress = SegmentRegister[SegNum].base + offset

при цьому отримана адреса перевіряється на обмеження, встановлене в сегменті. Деякі програми використовували не дуже стандартні декларації вказівників FAR та NEAR C / C ++, але багато хто просто сказав *T--- але були компілятори компілятора та лінкера, так що, наприклад, вказівники коду можуть бути поблизу покажчиків, лише 32-бітове зміщення проти всього, що знаходиться в регістр CS (сегмент коду), тоді як покажчики даних можуть бути покажчиками FAR, вказуючи як 16-бітний номер сегмента, так і 32-бітове зміщення для значення 48 біт. Тепер обидві ці кількості, безумовно, пов'язані з адресою, але оскільки вони не однакового розміру, яка з них адреса? Більше того, сегменти також містили дозволи - лише для читання, читання, запису, виконувані файли - на додаток до матеріалів, що стосуються фактичної адреси.

Більш цікавий приклад - IMHO - це сім'я IBM AS / 400. Цей комп'ютер одним із перших застосував ОС на C ++. Покажчики на цій машині зазвичай були в 2 рази фактичним розміром адреси - наприклад, як ця презентаціяговорить, 128 бітових покажчиків, але фактична адреса становила 48-64 біт, і, знову ж таки, деяка додаткова інформація, що називається здатністю, яка надала дозволи, такі як читання, запис, а також обмеження для запобігання переповненню буфера. Так: ви можете це зробити сумісно з C / C ++ - і якби це було всюдисущим, китайська PLA та слов'янська мафія не втручалися б у так багато західних комп'ютерних систем. Але історично більшість програм C / C ++ нехтували безпекою для продуктивності. Найцікавіше, що сімейство AS400 дозволило операційній системі створити захищені покажчики, які могли б надаватися непривілейованому коду, але які непривілейований код не міг підробити чи підробити. Знову-таки, безпека і, хоча стандарти відповідають, набагато неохайний нестандартний код C / C ++ не працюватиме в такій захищеній системі. Знову ж таки, існують офіційні стандарти,

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

Існує багато способів вирішення цього [проблеми, пов’язані з одиничним проти багаторазового наслідування та віртуальним успадкуванням]. Ось як компілятор Visual Studio вирішує обробляти це: вказівник на член-функцію класу, що перебуває у множині, насправді є структурою ". Вони продовжують говорити:" Лиття покажчика функції може змінити його розмір! ".

Як ви, напевно, здогадуєтесь з моєї понтифікації щодо (в) безпеки, я брав участь у проектах апаратного / програмного забезпечення C / C ++, де вказівник розглядався швидше як здатність, ніж необроблена адреса.

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

КОРОТКИЙ РЕЗЮМЕ (який я також поставлю вгорі):

(0) мислення покажчиків як адрес часто є хорошим інструментом навчання і часто є реальною реалізацією для покажчиків на звичайні типи даних.

(1) Але в багатьох, можливо більшості, вказівники компіляторів на функції не є адресами, а є більшими за адресу (як правило, 2X, іноді більше), або насправді вказівники на структуру в пам'яті, ніж містять адреси функції та інші речі постійний басейн.

(2) Покажчики на члени даних та покажчики методів часто навіть незнайоміші.

(3) Спадковий код x86 з проблемами вказівника FAR та NEAR

(4) Кілька прикладів, особливо IBM AS / 400, із захищеними "жировими покажчиками".

Я впевнений, що ви можете знайти більше.


У 16-бітному реальному режимі LinearAddress = SegmentRegister.Selector * 16 + Offset(зверніть увагу на 16, а не на 16). У захищеному режимі LinearAddress = SegmentRegister.base + offset(відсутнє будь-яке множення; база сегмента зберігається в GDT / LDT і зберігається в кешовому реєстрі сегментів як є ).
Олексій Фрунзе

Ви також правильні щодо бази сегментів. Я помилувався. Межа сегмента необов'язково кратна 4K. База сегмента просто потребує розшифровки обладнанням, коли він завантажує дескриптор сегмента з пам'яті в регістр сегментів.
Krazy Glew

4

Вказівник - це лише інша змінна, яка використовується для утримання адреси місця пам'яті (зазвичай, це пам'ять іншої змінної).


Отже, пуанті - це насправді адреса пам’яті? Ви не згодні з автором? Просто намагаюся зрозуміти.
d0rmLife

Основна функція вказівника - вказувати на щось. Як саме це досягнуто і чи є реальна адреса чи ні, не визначено. Вказівник може бути лише ідентифікатором / ручкою, а не реальною адресою.
Олексій Фрунзе

4

Ви можете бачити це таким чином. Вказівник - це значення, яке представляє адресу в адресному просторі пам'яті.


2
Вказівник не обов'язково повинен містити в ньому реальну адресу пам'яті. Дивіться мою відповідь та коментар (и) під нею.
Олексій Фрунзе

Що .... вказівник на першу змінну в стеці не друкує 0. він друкує верхню (або нижню) рамку стека залежно від способу її реалізації.
thang

@thang Для першої змінної верхня і нижня частина однакові. І яка адреса верхньої чи нижньої у цьому випадку стека?
Валентин Раду

@ValentinRadu, чому б ти не спробувавш .. очевидно, ти не пробував.
thang

2
@thang Ви маєте рацію, я зробив деякі дуже погані припущення, на мій захист тут 5 ранку.
Валентин Раду

3

Вказівник - це лише інша змінна, яка може містити адресу пам'яті, як правило, іншої змінної. Вказівник, що є змінною, також має адресу пам'яті.


1
Не обов'язково адресу. До речі, ви читали наявні відповіді та коментарі, перш ніж публікувати свою відповідь?
Олексій Фрунзе

3

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

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

Покажчики підпорядковуються правилам перетворення та забезпечують перевірку типу компіляції часу.

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

Вказівник може бути використаний як булева змінна: він перевіряє true, якщо він не є null, а false - якщо null.

Якщо машинною мовою, якщо нульовий вказівник є смішною адресою, наприклад 0xFFFFFFFF, можливо, вам доведеться мати явні тести на це значення. C приховує це від вас. Навіть якщо нульовий покажчик 0xFFFFFFFF, ви можете протестувати його за допомогою if (ptr != 0) { /* not null! */}.

Використання покажчиків, які підривають систему типів, призводить до невизначеної поведінки, тоді як подібний код на машинній мові може бути добре визначений. Асемблери збирають написані вами інструкції, але компілятори C оптимізують виходячи з припущення, що ви нічого не зробили неправильно. Якщо float *pвказівник вказує на long nзмінну і *p = 0.0виконується, компілятор для цього не потрібно. Подальше використання nне буде необхідним для зчитування бітового шаблону значення float, але, можливо, це буде оптимізований доступ, який базується на припущенні "суворого псевдоніму", nякого не торкалися! Тобто припущення, що програма добре ведеться, і тому pне слід вказувати n.

У C вказівники на код та вказівники на дані різні, але у багатьох архітектурах адреси однакові. Можуть бути розроблені компілятори C, які мають "жирні" вказівники, навіть якщо цільова архітектура цього не має. Жирові вказівники означають, що покажчики - це не просто адреси машини, але містять іншу інформацію, наприклад інформацію про розмір об'єкта, на який вказують, для перевірки меж. Портально написані програми легко портують до таких компіляторів.

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


Покажчики NULL працюють не так, як ви думаєте, на всіх платформах - дивіться мою відповідь на CiscoIPPhone вище. NULL == 0 - припущення, яке виконується лише на платформах на базі x86. Конвенція говорить, що нові платформи повинні відповідати x86, однак особливо у вбудованому світі це не так. Редагувати: Також C не робить нічого для абстрагування значення вказівного способу з апаратного забезпечення - "ptr! = 0" не працюватиме як тест NULL на платформі, де NULL! = 0.
DX-MON

1
DX-MON, це абсолютно неправильно для стандарту C. NULL розроблено як 0, і вони можуть бути взаємозамінні в операторах. Незалежно від того, чи є представлення вказівника NULL на апаратному рівні, це 0 біт, не має значення для того, як воно представлено у вихідному коді.
Марк Бессі

@ DX-MON Боюся, що ви не працюєте з правильними фактами. В C інтегральний постійний вираз виступає як константа нульового вказівника, незалежно від того, чи є нульовим покажчиком нульовий адресу. Якщо ви знаєте компілятор C, де ptr != 0це не нульовий тест, розкрийте його особу (але перш ніж це зробити, надішліть звіт про помилку постачальнику).
Каз

Я бачу, на що ви звертаєтесь , але ваші коментарі щодо нульових покажчиків є невідповідними, оскільки ви плутаєте вказівники та адреси пам'яті - саме те, що рекомендується уникати цитатою, вказаною у запитанні! Правильне твердження: C визначає нульовий покажчик на нуль, незалежно від того, чи законна адреса пам’яті зі зміщенням нуля законна чи ні.
alexis

1
@ Алексис Розділ та вірш, будь ласка. C не визначає нульовий покажчик до нуля. C визначає нуль (або будь-який інтегральний постійний вираз, значення якого дорівнює нулю) як синтаксис для позначення нульової постійної вказівника. faqs.org/faqs/C-faq/faq (розділ 5).
Каз

3

Перед розумінням покажчиків нам потрібно розібратися в об'єктах. Об'єкти є сутностями, які існують і мають специфікатор місцезнаходження, який називається адресою. Вказівник - це просто змінна, як і будь-які інші змінні у Cтипі, який називається pointer, вміст якого інтерпретується як адреса об'єкта, що підтримує наступну операцію.

+ : A variable of type integer (usually called offset) can be added to yield a new pointer
- : A variable of type integer (usually called offset) can be subtracted to yield a new pointer
  : A variable of type pointer can be subtracted to yield an integer (usually called offset)
* : De-referencing. Retrieve the value of the variable (called address) and map to the object the address refers to.
++: It's just `+= 1`
--: It's just `-= 1`

Вказівник класифікується на основі типу об'єкта, на який він в даний час посилається. Єдина частина важливої ​​інформації - це розмір об'єкта.

Будь-який об’єкт підтримує операцію &(адресу), яка отримує специфікатор місця розташування (адресу) об'єкта як тип об'єкта вказівника. Це повинно зменшити плутанину навколо номенклатури, оскільки це має сенс називати &операцією об'єкта, а не вказівником, результат якого типу - вказівник типу об'єкта.

Примітка У цьому поясненні я залишив концепцію пам'яті.


Мені подобається ваше пояснення щодо абстрактної реальності загального вказівника в загальній системі. Але, можливо, корисне обговорення пам’яті. Справді, кажучи для себе, я знаю, що це було б ...! Я думаю, що обговорення зв'язку може бути дуже корисним для розуміння широкої картини. +1 все
одно

@ d0rmLife: Ви маєте достатньо пояснень в інших відповідях, які висвітлюють більшу картину. Я просто хотів дати математичне абстрактне пояснення як інший погляд. Крім того, IMHO створить менше плутанини у виклику &як "Адреса", оскільки це більше прив'язане до Об'єкта, а не до вказівника як такого "
Абхіджіт

Без образи, але я вирішу для себе, яке достатнє пояснення. Одного підручника недостатньо для повного пояснення структур даних та розподілу пам'яті. ;) .... у будь-якому випадку, ваша відповідь все ще корисна , навіть якщо вона не нова.
d0rmLife

Немає сенсу обробляти покажчики без концепції пам’яті . Якщо об'єкт існує без пам'яті, він повинен знаходитися в місці, де немає адреси - наприклад, в регістрах. Щоб мати можливість використовувати "&", передбачається пам'ять.
Акі Суйконен

3

Адреса використовується для ідентифікації фрагмента зберігання фіксованого розміру, як правило, для кожного байта, як цілого числа. Це точно називається байтовою адресою , яку також використовує ISO C. Існують деякі інші методи побудови адреси, наприклад для кожного біта. Однак так часто використовується лише байт-адреса, ми зазвичай опускаємо "байт".

Технічно адреса ніколи не є значенням C, оскільки визначення терміна "значення" в (ISO) C є:

точне значення вмісту об'єкта, коли його трактують як певний тип

(Підкреслюється мною.) Однак такого типу "адреси" у C. немає.

Вказівник не той. Покажчик - це різновид типу мови С. Існує кілька різних типів вказівника. Вони не обов'язково підкорятися однаковим набором правил мови, наприклад , ефект ++на значення типу int*VS. char*.

Значення в C може мати тип вказівника. Це називається значенням вказівника . Щоб було зрозуміло, значення вказівника не є покажчиком на мові С. Але ми звикли їх змішувати разом, тому що в C це, швидше за все, не буде неоднозначним: якщо ми називаємо вираз p"покажчиком", це просто значення вказівника, але не тип, оскільки названий тип в C не є виражається виразом , але іменем типу або іменем typedef .

Деякі інші речі тонкі. Як користувач C, по-перше, слід знати, що objectозначає:

область зберігання даних у середовищі виконання, вміст якої може представляти значення

Об'єкт - це сутність, яка представляє значення, що мають певний тип. Вказівник - тип об'єкта . Отже, якщо ми оголосимо int* p;, тоді pозначає "об'єкт типу вказівника" або "об'єкт вказівника".

Примітка немає ні «змінна» нормативному визначається стандартом (насправді він ніколи не використовується в якості іменника по ISO C в нормативному тексті). Однак неофіційно ми називаємо об'єкт змінною, як це робить і інша мова. (Але все ще не так точно, наприклад, у C ++ змінна може бути еталонним типом нормативно, що не є об'єктом.) Фрази "вказівний об'єкт" або "змінна вказівник" іноді трактуються як "значення вказівника", як вище, з ймовірна незначна різниця. (Ще один набір прикладів - "масив".)

Оскільки вказівник - це тип, а адреса ефективно "безтипова" в С, значення вказівника приблизно "містить" адресу. І вираз типу вказівника може дати адресу, наприклад

ISO C11 6.5.2.3

3 Одинарний &оператор вказує адресу свого операнда.

Зауважте, це формулювання введено у WG14 / N1256, тобто ISO C99: TC3. У С99 є

3 Онарний &оператор повертає адресу свого операнда.

Він відображає думку комітету: адреса не є значенням вказівника, поверненим одинарним &оператором.

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

ISO C11 6.6

9 Константа адреси - це нульовий покажчик, вказівник на значення, що позначає об'єкт статичної тривалості зберігання, або покажчик на позначення функції

ISO C ++ 11 5.19

3 ... Постійний вираз адреси - це постійне вираження основного ядра типу вказівника, яке оцінює адресу об'єкта зі статичною тривалістю зберігання, адресою функції, або нульовим значенням вказівника, або постійним виразом основного ядра первинного значення типу std::nullptr_t. ...

(В останніх стандартних чернетках C ++ використовується інше формулювання, щоб не виникало цієї проблеми.)

Насправді і "константа адреси" в C, і "постійний вираз адреси" в C ++ є постійним вираженням типів вказівників (або принаймні "типів, подібних до вказівника", починаючи з C ++ 11).

І вбудований одинарний &оператор в C і C ++ називається "адресою"; аналогічно std::addressofвводиться в C ++ 11.

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


2

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


2

Подумайте над цим, я думаю, що це питання семантики. Я не думаю, що автор є правильним, оскільки стандарт C посилається на покажчик як на адресу адреси до посиланого об'єкта, як тут уже згадували інші. Однак адреса! = Адреса пам'яті. Адреса може бути справді будь-якою відповідно до стандарту C, хоча вона в кінцевому підсумку призведе до адреси пам'яті, сам покажчик може бути ідентифікатором, селектором зміщення + (x86), насправді будь-яким, доки він може описати (після відображення) будь-яку пам'ять адреса в адресному просторі.


Вказівник містить адресу (або ні, якщо вона є нульовою). Але це далеко не те, що це адреса: наприклад, два вказівники на одну і ту ж адресу, але з іншим типом не є рівнозначними у багатьох ситуаціях.
Жил "ТАК - перестань бути злим"

@Gilles Якщо ви бачите «бути», як і в int i=5-> я є 5 , то покажчик є адреса так. Також у null є адреса. Зазвичай недійсна адреса запису (але не обов'язково, див. Режим x86-real), але все-таки адреса. Насправді існує лише 2 вимоги до нуля: гарантовано порівняти нерівне вказівник з фактичним об'єктом, а будь-які два нульові покажчики порівнятимуть рівними.
Валентин Раду

Навпаки, нульовий вказівник гарантовано не дорівнює адресу будь-якого об’єкта. Визначення нульового покажчика - це невизначена поведінка. Велика проблема сказати, що "покажчик - це адреса" полягає в тому, що вони працюють по-різному. Якщо pвказівник, p+1не завжди адреса збільшується на 1.
Жил "ТАК - перестань бути злим"

Читати ще раз коментар , будь ласка, it's guaranteed to compare unequal to a pointer to an actual object. Щодо арифметики вказівника, я не бачу сенсу, значення вказівника все-таки є адресою, навіть якщо операція "+" не обов'язково додасть до неї один байт.
Валентин Раду

1

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

Компілятор може припустити, що вказівники несумісних типів не вказують на одну і ту ж адресу, навіть якщо вони це чітко роблять, що може спричинити поведінку, яка була б неможливою для простої моделі вказівника == адреси. Розглянемо наступний код (припустимо sizeof(int) = 2*sizeof(short)):

unsigned int i = 0;
unsigned short* p = (unsigned short*)&i;
p[0]=p[1]=1;

if (i == 2 + (unsigned short)(-1))
{
  // you'd expect this to execute, but it need not
}

if (i == 0)
{
  // you'd expect this not to execute, but it actually may do so
}

Зауважте, що існує виняток для char* , тому маніпулювання значеннями char*можливо (хоча і не дуже портативно).


0

Короткий підсумок: адреса змінного струму - це значення, як правило, представлене як адреса пам'яті на рівні машини з певним типом.

Некваліфіковане слово «вказівник» неоднозначне. C має об'єкти вказівника (змінні), типи вказівників, вирази вказівника та значення вказівника .

Дуже часто вживати слово "вказівник" для позначення "вказівний об'єкт", і це може призвести до певної плутанини - саме тому я намагаюся використовувати "вказівник" як прикметник, а не як іменник.

Стандарт C, принаймні в деяких випадках, використовує слово "покажчик", щоб означати "значення вказівника". Наприклад, в описі malloc йдеться, що він "повертає або нульовий покажчик, або покажчик на виділений простір".

То яка адреса в C? Це значення вказівника, тобто значення певного типу вказівника. (За винятком того, що значення нульового вказівника не обов'язково позначається як "адреса", оскільки це не адреса нічого).

В описі стандарту одинарного &оператора зазначено, що він "дає адресу свого операнду". Поза стандартом C слово "адреса" зазвичай використовується для позначення (фізичної або віртуальної) адреси пам'яті, як правило, одного розміру слова (незалежно від того, яке "слово" є в даній системі).

"Адреса" змінного струму зазвичай реалізується як машинна адреса - подібно до того, як intзначення C зазвичай реалізується як машинне слово. Але адреса С (значення вказівника) - це не просто адреса машини. Це значення, як правило, представлене як машинна адреса, і це значення з певним типом .


0

Значення вказівника - це адреса. Змінна вказівника - це об'єкт, який може зберігати адресу. Це правда, тому що саме такий стандарт визначає покажчик. Важливо сказати це новачкам C, оскільки новачкам C часто незрозуміло різницю між вказівником та річчю, на яку він вказує (тобто вони не знають різниці між конвертом та будівлею). Поняття адреси (у кожного об'єкта є адреса, і саме на це зберігається вказівник) важливо, оскільки це сортує це.

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

Він продовжує говорити про "цілком розумну маніпуляцію адреси". Що стосується стандарту С, то в принципі немає такого поняття, як "абсолютно розумна маніпуляція адресою". Додавання визначається вказівниками, і це в основному це. Звичайно, ви можете перетворити вказівник на ціле число, виконати кілька побітових чи арифметичних операцій, а потім перетворити його назад. Це не гарантовано працює за стандартом, тому перед тим, як писати цей код, вам краще знати, як ваша конкретна реалізація C представляє покажчики та виконує це перетворення. Він, ймовірно, використовує представлення адреси, яке ви очікуєте, але це не ваша вина, тому що ви не прочитали посібник. Це не плутанина, це неправильна процедура програмування ;-)

Коротше кажучи, C використовує більш абстрактне поняття адреси, ніж автор.

Авторська концепція адреси, звичайно, також не є найнижчим рівнем у цьому питанні. Що стосується карт віртуальної пам’яті та фізичної адреси RAM на декількох мікросхемах, то число, яке ви повідомляєте процесору, - це «адреса магазину», до якої ви хочете отримати доступ, в основному не має нічого спільного з тим, де фактично розташовані потрібні дані в апаратному забезпеченні. Це всі шари непрямості та репрезентації, але автор обрав один із привілеїв. Якщо ви збираєтеся це робити, коли говорити про C, виберіть рівень C, щоб отримати привілей !

Особисто я не вважаю, що зауваження автора є дуже корисними, за винятком контексту ознайомлення з C програмовами. Тим, хто йде з мов вищого рівня, звичайно, не корисно сказати, що значення вказівника - це не адреси. Було б набагато краще визнати складність, ніж сказати, що ЦП має монополію говорити, що таке адреса, і таким чином, що значення вказівника C "не є" адресами. Це адреси, але вони можуть бути написані іншою мовою від адрес, які він має на увазі. Визначення двох речей у контексті C як "адреса" та "адреса магазину" було б адекватним, я думаю.


0

Простіше сказати, покажчики фактично зміщують частину механізму сегментації, який переходить на лінійну адресу після сегментації, а потім на фізичну адресу після підкачки. Фізичні адреси фактично адресовані вами таран.

       Selector  +--------------+         +-----------+
      ---------->|              |         |           |
                 | Segmentation | ------->|  Paging   |
        Offset   |  Mechanism   |         | Mechanism |
      ---------->|              |         |           |
                 +--------------+         +-----------+
        Virtual                   Linear                Physical
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.