Візьмемо адресу елемента масиву "минулий кінець" за допомогою індексу: законний за стандартом C ++ чи ні?


77

Я вже кілька разів бачив, як стверджується, що наступний код заборонений стандартом С ++:

int array[5];
int *array_begin = &array[0];
int *array_end = &array[5];

Чи є &array[5]законний код C ++ у цьому контексті?

Я хотів би отримати відповідь із посиланням на Стандарт, якщо це можливо.

Також було б цікаво дізнатись, чи відповідає він стандарту C. І якщо це не стандартний C ++, чому було прийнято рішення ставитися до нього інакше, ніж array + 5або &array[4] + 1?


7
@Brian: Ні, це вимагатиме лише перевірки меж, якщо час виконання вимагався для виявлення помилки. Щоб уникнути цього, стандарт може просто сказати "заборонено". Це невизначена поведінка у найкращому вигляді. Вам заборонено це робити, а середовище виконання та компілятор не зобов’язані повідомляти вас, якщо ви це робите.
jalf

Гаразд, лише трохи уточнити, бо заголовок ввів мене в оману. Вказівники поза межами заборонені загалом, але стандарт набагато м’якший з односторонніми покажчиками. Можливо, вам захочеться відредагувати заголовок, якщо ви спеціально запитуєте про одноразові вказівники. Якщо ви хочете знати про вказівники за межами межі загалом , вам слід відредагувати свій приклад. ;)
jalf

Він взагалі не питає про вказівники на минуле. Він запитує про використання оператора & для отримання вказівника.
Метью Флашен

1
@Matthew: Але відповідь на це залежить від того, куди вказує цей вказівник. Вам дозволено брати адресу в одному випадку, але не у випадку, що виходить за межі.
jalf

Розділ 5.3.1.1 Унарний оператор '*': 'результат є значенням l, що посилається на об'єкт або функцію'. Розділ 5.2.1 Передплата Вираз E1 [E2] ідентичний (за визначенням) to ((E1) + (E2)). По моєму прочитанню стандарту тут. Відхилення відображення результуючого вказівника не відбувається. (див. повне пояснення нижче)
Мартін Йорк,

Відповіді:


40

Ваш приклад легальний, але лише тому, що ви насправді не використовуєте вказівник поза межами.

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

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

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

Ось що має сказати стандарт на цю тему:

5,7: 5:

Коли вираз, який має інтегральний тип, додається або віднімається від покажчика, результат має тип операнда покажчика. Якщо операнд вказівника вказує на елемент об'єкта масиву, а масив досить великий, результат вказує на зміщення елемента від вихідного елемента таким чином, що різниця індексів результуючого та вихідного елементів масиву дорівнює інтегральному вираженню. Іншими словами, якщо вираз P вказує на i-й елемент об'єкта масиву, вирази (P) + N (еквівалентно N + (P)) та (P) -N (де N має значення n) вказують відповідно до i + n-го та i-n-го елементів об'єкта масиву за умови їх існування. Більше того, якщо вираз P вказує на останній елемент об'єкта масиву, вираз (P) +1 вказує один минулий останній елемент об'єкта масиву, а якщо вираз Q вказує один минулий останній елемент об'єкта масиву, вираз (Q) -1 вказує на останній елемент об'єкта масиву . Якщо і операнд покажчика, і результат вказують на елементи одного і того ж об'єкта масиву або одного з останніх елементів об'єкта масиву, обчислення не повинно виробляти перевищення;в іншому випадку поведінка не визначена .

(курсив мій)

Звичайно, це для оператора +. Тож, щоб бути впевненим, ось що говорить стандарт про підписку на масив:

5.2.1: 1:

Вираз E1[E2]ідентичний (за визначенням)*((E1)+(E2))

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

[Примітка: наприклад, адреса, що стоїть за кінцем масиву (5.7), буде вважатися такою, що вказує на непов’язаний об’єкт типу елемента масиву, який може знаходитись за цією адресою. —Кінець примітки]

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

Дякуємо ilproxyil за виправлення останнього біта тут, відповідь на останню частину вашого запитання:

  • array + 5насправді нічого не розмежовує, він просто створює вказівник на той, що минув кінець array.
  • &array[4] + 1dereferences array+4(що абсолютно безпечно), бере адресу цього значення і додає до цієї адреси одну, що призводить до одноразового вказівника (але цей вказівник ніколи не отримує посилання.
  • &array[5] dereferences array + 5 (що, наскільки я бачу, є законним і призводить до "непов’язаного об’єкта типу елемента масиву", як було сказано вище), а потім приймає адресу цього елемента, що також здається досить законним.

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


4
& array [5] вказує на одне минуле. Однак це не законний спосіб отримати цю адресу.
Метью Флашен

5
останнє речення неправильне. "масив + 5" та "& масив [4] + 1" НЕ розмежовують один кінець, тоді як масив [5] РОБИТЬ. (я також припускаю, що ви мали на увазі & array [5], але коментар все ще стоїть). Перші два просто вказують один кінець.
user83255

2
@jalf, що примітка - я думаю, він просто хоче сказати, що "a + sizeof a" однаково справедливо для "& b", якщо b безпосередньо виділено після масиву "a", і що отримані адреси однаково "вказують" на те саме об'єкт. НЕ більш. Пам'ятайте, що всі примітки є інформативними (ненормативними): якщо в ній будуть вказані такі фундаментальні важливі факти, як те, що після об'єкта масиву є об'єкти, розташовані в кінці, то таке правило потрібно зробити нормативним
Йоганнес Шауб - літб

2
Що стосується ANSI C (C89 / C90), це правильна відповідь. Якщо дотримуватися стандарту, буква & array [5] є технічно недійсною, тоді як масив + 5 є дійсним, навіть незважаючи на те, що майже кожен компілятор створить однаковий код для обох виразів. C99 оновлює стандарт, щоб явно дозволити & масив [5]. Детальнішу інформацію див. У моїй відповіді.
Адам Розенфілд,

2
@jalf, а також увесь текст, у якому є примітка, починається з "Якщо об'єкт типу T знаходиться за адресою A ..." <- Це означає "У наступному тексті передбачається, що є об'єкт за адресою A." Тож у вашій цитаті не (і не може, за цієї умови) сказано, що за адресою А. завжди є об’єкт.
Йоганнес Шауб - litb

44

Так, це законно. З проекту стандарту C99 :

Пункт 6.5.5.1, пункт 2:

Вираз постфікса, за яким слідує вираз у квадратних дужках, []є підписаним позначенням елемента масиву. Визначення індексу оператора [] є те , що E1[E2]ідентично (*((E1)+(E2))). Через правила перетворення, які застосовуються до двійкового +оператора, if E1є об'єктом масиву (еквівалентно покажчиком на початковий елемент об'єкта масиву) і E2є цілим числом, E1[E2]позначає E2-й елемент E1(відлік від нуля).

§ 6.5.5.2, абзац 3 (наголос на моєму):

Унарний &оператор видає адресу свого операнда. Якщо операнд має тип '' тип '', результат має тип '' вказівник на тип ''. Якщо операнд є результатом унарного *оператора, ні цей оператор, ні &оператор не обчислюються, і результат такий, ніби обидва пропущені, за винятком того, що обмеження для операторів все ще застосовуються, і результат не є значенням. Подібним чином, якщо операнд є результатом []оператора, не обчислюється ні оператор &, ні одинарність, *що випливає з [], і результат виглядає так, ніби &оператор було видалено, а []оператор змінено на +оператор. В іншому випадку результат є покажчиком на об’єкт або функцію, позначену його операндом.

Пункт 6.5.5, пункт 8:

Коли вираз, який має цілочисельний тип, додається або віднімається від покажчика, результат має тип операнда вказівника. Якщо операнд вказівника вказує на елемент об’єкта масиву, а масив досить великий, результат вказує на зміщення елемента від вихідного елемента таким чином, що різниця індексів результуючого та вихідного елементів масиву дорівнює цілому виразу. Іншими словами, якщо вираз Pвказує на i-th елемент об'єкта масиву, вирази (P)+N(еквівалентно, N+(P)) та (P)-N(де Nмає значення n) вказують відповідно на i+n-th та i−n-th елементи об'єкта масиву за умови існувати. Більше того, якщо виразPвказує на останній елемент об’єкта масиву, вираз (P)+1вказує один минулий останній елемент об’єкта масиву, а якщо вираз Qвказує один минулий останній елемент об’єкта масиву, вираз (Q)-1вказує на останній елемент об’єкта масиву. Якщо і операнд вказівника, і результат вказують на елементи одного і того ж об'єкта масиву або одного з останніх елементів об'єкта масиву, обчислення не повинно спричинити переповнення; в іншому випадку поведінка не визначена. Якщо результат вказує один за останнім елементом об'єкта масиву, він не повинен використовуватися як операнд одинарного *оператора, який обчислюється.

Зверніть увагу, що стандарт явно дозволяє вказівникам спрямовувати один елемент за кінець масиву, за умови, що вони не розмежовуються . За 6.5.2.1 та 6.5.3.2 вираз &array[5]еквівалентний &*(array + 5), що еквівалентно (array+5), який вказує один за другим кінцем масиву. Це не призводить до припинення користування (відповідно до 6.5.3.2), тому це є законним.


1
Він прямо запитав про C ++. Це така тонка різниця, на яку не можна покладатися при перенесенні між ними.
Метью Флашен

3
Він запитав про обидва: "Також було б цікаво дізнатись, чи відповідає він стандарту С".
CB Bailey

2
@Matthew Flaschen: Стандарт C ++ включає стандарт C за допомогою посилання. Додаток С.2 містить перелік змін (несумісність між ISO C та ISO C ++), і жодна із змін не стосується цих пунктів. Отже, & array [5] є законним для C та C ++.
Адам Розенфілд,

12
Стандарт С є нормативним посиланням на стандарт С ++. Це означає, що положення стандарту С, на які посилається стандарт С ++, є частиною стандарту С ++. Це не означає, що застосовується все, що відповідає стандарту C. Зокрема, Додаток С є інформативним, а не нормативним, тому те, що різниця не виділена в цьому розділі, не означає, що „версія” С застосовується до С ++.
CB Bailey

1
@ Адам: Дякую, що вказали на це. Я ніколи не був повністю впевнений в історії С. Щодо C ++ 0x, що стосується C99, я мало знаю про зміни в C99, я майже впевнений, що C ++ і надалі буде посилатися на C89 / 90 і буде вибирати "бажані" зміни з C99 для кожного випадку окремо. . Це запитання / відповідь є хорошим прикладом цього. Я б сказав, що C ++ і надалі використовуватиме значення "lvalue-to-rvalue", отже, ніякої невизначеної поведінки, а не інтегрування формулювання "& * == no-op".
Річард Корден,

17

Це є законним.

Відповідно до документації gcc для C ++ , &array[5]це законно. І в C ++, і в C ви можете безпечно звернутися до елемента поза кінцем масиву - ви отримаєте дійсний вказівник. Отже, &array[5]як вираз є законним.

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

На практиці, я думаю, це, як правило, не спричиняє аварії.

Редагувати: До речі, загалом саме так реалізується ітератор end () для контейнерів STL (як вказівник на минулий кінець), тож це досить гарне свідчення законності практики.

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


4
Я б сказав, що ви маєте рацію, лише тоді, коли специфікація C ++ не говорить про те, що & * потрібно розглядати як заборону. Я думаю, це, мабуть, не говорить про це.
Тайлер Макгенрі

8
сторінка, на яку ви посилаєтесь (правильно), говорить, що правомірно вказувати один кінець. & array [5], спочатку технічно перенаправляє (масив + 5), потім знову посилається на нього. Тож технічно це так: (& * (array + 5)). На щастя, компілятор достатньо розумний, щоб знати, що & * не можна розкласти на нічого. Однак вони не повинні цього робити, тому, я б сказав, це UB.
Еван Теран

4
@ Еван: У цьому є ще щось. Ознайомтесь із останнім рядком основного випуску 232: std.dkuug.dk/JTC1/SC22/WG21/docs/cwg_active.html#232 . Останній приклад там просто виглядає неправильно - але вони чітко пояснюють, що різниця полягає у перетворенні "lvalue-to-rvalue", яке в цьому випадку не має місця.
Річард Корден,

@Richard: цікаво, здається, є певні суперечки на цю тему. Я навіть погодився б, що це слід дозволити :-P.
Еван Теран

2
Це той самий тип невизначеної поведінки, що і "посилання на НУЛЬ", про що люди продовжували дискутувати, і де, мабуть, усі проголосували відповідь, сказавши "це невизначена поведінка"
Йоханнес Шауб - litb

10

Я вважаю, що це законно, і це залежить від конверсії "lvalue to rvalue". Останній рядок Основного випуску 232 має таке:

Ми погодились, що підхід у стандарті здається нормальним: p = 0; * р; не є по суті помилкою. Перетворення lvalue-to-rvalue призведе до невизначеної поведінки

Хоча це дещо інший приклад, він показує, що '*' не призводить до перетворення lvalue в rvalue і тому, враховуючи, що вираз є безпосереднім операндом '&', який очікує lvalue, тоді поведінка визначена.


+1 за цікаве посилання. Я все ще не впевнений, що згоден з тим, що p = 0; * p; є чітко визначеним, оскільки я не впевнений, що '*' добре визначений для виразу, значення якого не є вказівником на фактичний об'єкт.
CB Bailey

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

1
Зверніть увагу, що статус цього випуску все ще «складається», і він ще не увійшов до стандарту (поки що), принаймні ті чорнові версії C ++ 11 та C ++ 14, які я міг знайти.
musiphil

Пропозиція "порожніх оцінок" ніколи не була прийнята в жоден опублікований стандарт
М.М.

Примітки у випуску прямо посилаються на &*a[n]проблему: "Подібним чином, дозволяється переорієнтування вказівника на кінець масиву, доки значення не використовується". На жаль, ця проблема триває з 2003 року, і її рішення ще не входить до стандарту.
Пабло Гальперн,

9

Я не вважаю, що це незаконно, але я вважаю, що поведінка & array [5] не визначена.

  • 5.2.1 [expr.sub] E1 [E2] ідентичний (за визначенням) * ((E1) + (E2))

  • 5.3.1 Оператор [expr.unary.op] unary * ... результат - значення l, що посилається на об'єкт або функцію, на яку вказує вираз.

На даний момент у вас невизначена поведінка, оскільки вираз ((E1) + (E2)) насправді не вказував на об'єкт, а стандарт дійсно говорить, яким має бути результат, якщо це не так.

  • 1.3.12 [defns.undefined] Невизначена поведінка також може очікуватися, коли цей Міжнародний стандарт опускає опис будь-якого явного визначення поведінки.

Як зазначалося в інших місцях, array + 5і &array[0] + 5є дійсними і чітко визначеними способами отримання покажчика за кінцем масиву.


Ключовий момент: "результат '*' є значенням". З того, що я можу сказати, це стає лише UB, якщо у вас є перетворення lvalue в rvalue на цей результат.
Річард Корден,

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

7

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


4
+1 щодо залучення оператора & до обговорення, навіть якщо експерти рекомендують ніколи не перевизначати його, оскільки деякі контейнери STL залежать від того, що він повертає вказівник на елемент. Це одна з тих речей, яка увійшла в стандарт ще до того, як вони дізналися краще.
Девід Родрігес - dribeas

4

Це законно:

int array[5];
int *array_begin = &array[0];
int *array_end = &array[5];

Розділ 5.2.1 Передплата Вираз E1 [E2] ідентичний (за визначенням) fi ((E1) + (E2))

Отже, цим можна сказати, що array_end теж еквівалентний:

int *array_end = &(*((array) + 5)); // or &(*(array + 5))

Розділ 5.3.1.1 Одинарний оператор '*': Одинарний * оператор виконує опосередкування: вираз, до якого він застосовується, повинен бути покажчиком на тип об'єкта або покажчиком на тип функції, а результат - значення l, що відноситься до об'єкта або функція, на яку вказує вираз. Якщо тип виразу "вказівник на Т", то результат результату - "Т." [Примітка: вказівник на неповний тип (відмінний від cv void) може бути розменований. Отримане таким чином значення lvalue можна використовувати обмежено (наприклад, для ініціалізації посилання); це значення l не слід перетворювати на значення r, див. 4.1. - кінцева примітка]

Важлива частина вищезазначеного:

'результат - значення lvalue, що посилається на об'єкт або функцію'.

Одинарний оператор '*' повертає значення lvalue, яке посилається на int (без дефеференції). Потім одинарний оператор '&' отримує адресу значення.

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

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

У розділі коментарів нижче подано два аргументи:

(будь ласка, прочитайте: але це довго, і ми в кінцевому підсумку троллі)

Аргумент 1

це є незаконним через пункт 5 розділу 5.7

Коли вираз, який має інтегральний тип, додається або віднімається від покажчика, результат має тип операнда покажчика. Якщо операнд вказівника вказує на елемент об'єкта масиву, а масив досить великий, результат вказує на зміщення елемента від вихідного елемента таким чином, що різниця індексів результуючого та вихідного елементів масиву дорівнює інтегральному вираженню. Іншими словами, якщо вираз P вказує на i-й елемент об'єкта масиву, вирази (P) + N (еквівалентно N + (P)) та (P) -N (де N має значення n) вказують відповідно до i + n-го та i - n-го елементів об'єкта масиву за умови їх існування. Більше того, якщо вираз P вказує на останній елемент об'єкта масиву, вираз (P) +1 вказує один минулий останній елемент об'єкта масиву, і якщо вираз Q вказує один за останнім елементом об'єкта масиву, вираз (Q) -1 вказує на останній елемент об'єкта масиву. Якщо і операнд покажчика, і результат вказують на елементи одного і того ж об'єкта масиву або одного з останніх елементів об'єкта масиву, обчислення не повинно викликати переповнення; в іншому випадку поведінка не визначена.

І хоча розділ є актуальним; він не демонструє невизначеної поведінки. Всі елементи масиву, про які ми говоримо, знаходяться або в масиві, або поза кінцем (що добре визначено в наведеному вище абзаці).

Аргумент 2:

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

Хоча доступ до пам'яті після закінчення масиву, безумовно, невизначений. Я не впевнений у unary * operatorдоступі до пам'яті (читання / запис у пам'ять) у цьому контексті (не так, як це визначає стандарт). У цьому контексті (як визначено стандартом (див. 5.3.1.1)) unary * operatorповертається a lvalue referring to the object. На моє розуміння мови, це не доступ до основної пам'яті. Потім результат цього виразу негайно використовується unary & operatorоператором, який повертає адресу об'єкта, на який посилається lvalue referring to the object.

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

Висновок:

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

  1. Залиште відповідь.
  2. Якщо поставити всі шапки, це дурне, і я помиляюся, щоб усі читали.

Це не аргумент:

Не все у всьому світі визначається стандартом С ++. Відкрий свій розум.


2
На думку кого? За яким уривком? *Виконує розіменування. Це оператор розмежування. Це те, що воно робить. Можливо, той факт, що ви потім отримуєте новий вказівник на отримане значення (використовуючи &), не має значення. Ви не можете просто представити послідовність оцінки, представити кінцеву семантику виразу і робити вигляд, що проміжні кроки не відбулися (або що правила мови не стосуються кожного).
Гонки легкості на орбіті

3
Я цитую той самий уривок: the result is an lvalue referring to the object or function to which the expression points.Зрозуміло, що якщо такого об'єкта не існує, для цього оператора не визначено поведінки. Ваше подальше твердження is returning a lvalue referring to the int (no de-refeference)- це те, що для мене не має сенсу. Чому ви вважаєте, що це не розрахування?
Гонки легкості на орбіті

2
It returns a reference to what is being pointed at.Що це, як не розшарування? В уривку сказано, що *здійснюється опосередкування, а непряме від вказівника до вказівника називається розмежуванням. Ваш аргумент по суті стверджує, що покажчики та посилання - це одне і те ж або, принаймні, неявно пов’язані, що просто не відповідає дійсності. int x = 0; int* ptr = &x; int& y = *x;Тут я розраховуюсь x. Мені не потрібно використовувати y, щоб це було правдою.
Гонки легкості на орбіті

2
@LokiAstari: У мене запитання, що, на вашу думку, означає " *розмежування посилань ", якщо не "виклик унарного оператора, який повертає значення lvalue, що посилається на об'єкт, на який вказує вираз"? (Зверніть увагу, що наступне речення стандарту вказує на цей процес як
розмежування посилань у

2
@LokiAstari: Я показав вам посилання дні та дні тому ; ви просто відмовляєтесь визнати, що вони існують, з якихось причин. Те, як ви можете виправдати цю поведінку, мені не під силу, але ви, мабуть, тролінг.
Гонки легкості на орбіті

2

Робочий проект ( n2798 ):

"Результат унарного & оператора - це вказівник на його операнд. Операндом має бути значення l або кваліфікований ідентифікатор. У першому випадку, якщо тип виразу" T ", тип результату" вказівник на Т. ”" (стор. 103)

масив [5] не є кваліфікованим ідентифікатором, наскільки я можу зрозуміти (список на стор. 87); здається, що найближчим є ідентифікатор, але хоча масив є ідентифікатором, масив [5] - ні. Це не значення l, оскільки «Значення l означає значення об’єкта або функції» (с. 76). array [5], очевидно, не є функцією, і не гарантується посилання на дійсний об'єкт (оскільки масив + 5 знаходиться після останнього виділеного елемента масиву).

Очевидно, що це може працювати в певних випадках, але це не дійсний C ++ або безпечний.

Примітка: Дозволено додавати, щоб отримати один за масивом (стор. 113):

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

Але це не законно робити це за допомогою &.


1
Я підтримав вас, бо ви праві. Немає гарантовано розташованого об’єкта в самому кінці місця. Людина, яка проголосувала за вас, ймовірно, неправильно вас зрозуміла (ви звучите так, ніби ви говорите, що будь-який масив-index-op взагалі не стосується жодного об’єкта). Я думаю , що тут цікава річ: Це є іменують, але і зовсім НЕ відноситься до об'єкту. І отже, тут є суперечність тому, що сказано в стандарті. Отже, це дає невизначену поведінку :) Це також пов’язано з цим: open-std.org/jtc1/sc22/wg21/docs/cwg_active.html#232
Йоханнес Шауб - litb

2
@jalf, у примітці сказано, "що може бути розташоване за цією адресою". Не гарантується, що там є один, який знаходиться :)
Йоганнес Шауб - літб

1
Стандарт говорить, що результат op * повинен бути значенням l, але він говорить лише про те, що це значення, якщо операнд є покажчиком, який фактично вказує на об'єкт. Це означало б (химерно), що якби мимо кінця випадково не вказували на підходящий об'єкт, реалізація повинна була б знайти відповідне значення l з іншого місця і використати це. Це справді зіпсує & масив [sizeof array]!
CB Bailey

3
"Однак я все ще думаю, що це не значення l. Оскільки немає об'єкта, який гарантовано знаходиться в масиві [5], масив [5] не може легально / посилатися / на об'єкт." <- Саме тому я вважаю, що це невизначена поведінка: вона спирається на деяку поведінку, не визначену явно стандартом, і, отже, потрапляє до 1.3.12 [defns.undefined]
Йоханнес Шауб - litb

1
літб, досить справедливо. Скажімо, це / не визначено / значення, а отже / точно не / на 100% безпечне.
Метью Флашен

2

Навіть якщо це законно, чому відходити від конвенції? масив + 5 і так коротший, і на мій погляд, читабельніший.

Редагувати: якщо ви хочете, щоб це було симетрично, ви можете писати

int* array_begin = array; 
int* array_end = array + 5;

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

Щоб бути симетричним, я думаю, що це має бути array_begin = array + 0; array_end = масив + 5; Як це для довго затримуваної відповіді на коментар?
Zan Lynx

Це може бути світовий рекорд :)
rlbond

1

Це має бути невизначеною поведінкою з наступних причин:

  1. Спроба отримати доступ до зовнішніх елементів призводить до невизначеної поведінки. Отже, стандарт не забороняє реалізацію, що створює виняток у такому випадку (тобто межі перевірки реалізації перед доступом до елемента). Якби це & (array[size])було визначено як begin (array) + sizeтаке, реалізація, що видає виняток у разі необмеженого доступу, більше не відповідала б стандарту.

  2. Це неможливо зробити, end (array)якщо масив не є масивом, а скоріше довільним типом колекції.


0

Стандарт С ++, 5.19, параграф 4:

Вираз адресної константи - це вказівник на значення lvalue .... Покажчик повинен бути створений явно, використовуючи унарний & оператор ... або використовуючи вираз типу масиву (4.2) ... Оператор підписки [] ... може бути використаний при створенні виразу адресної константи, але значення об'єкта не має доступу до використання цих операторів. Якщо використовується оператор підписки, один з його операндів повинен бути інтегральним постійним виразом.

Мені здається, що & array [5] є законним C ++, будучи виразом адреси.


1
Я не впевнений, що вихідне питання обов'язково стосується масиву зі статичним сховищем. Навіть якщо це так, мені цікаво, чи & array [5] не є адресним постійним виразом саме тому, що він не вказує на значення lvalue, що позначає об'єкт?
CB Bailey

Я не думаю, що це має значення, чи є масив статичним чи виділеним стеком.
Девід Торнлі,

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

Ваша пропозиція говорить, що якщо &array[5]це законно і посилається на статичний масив тривалості зберігання, то це буде константа адреси. (Порівняйте, &array[99]наприклад, жоден текст у цьому абзаці не розрізняє ці два випадки).
MM

-1

Якщо ваш приклад - НЕ загальний випадок, а конкретний, то це дозволено. Ви можете легально , AFAIK, пройти повз виділений блок пам'яті. Це не працює для загального випадку, однак, тобто, коли ви намагаєтесь отримати доступ до елементів далі на 1 від кінця масиву.

Щойно шукали C-Faq: текст посилання


верхня відповідь говорить "це законно", і я також кажу те саме. Чому тоді голос проти :). Щось не так із моєю відповіддю?
Aditya Sehgal

-2

Це цілком законно.

Клас шаблону vector <> зі stl робить саме це, коли ви викликаєте myVec.end (): він отримує вам вказівник (тут як ітератор), який спрямовує один елемент за кінець масиву.


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