Арифметика вказівника для недійсного вказівника в С


176

Коли покажчик до певного типу (скажімо int, char, float, ..) збільшується, його значення збільшується на розмір цього типу даних. Якщо voidвказівник, який вказує на дані про розмір x, збільшується, як це зробити, щоб вказувати xбайти вперед? Як компілятор знає додати xзначення покажчика?



3
Питання звучить так, ніби передбачає, що компілятор (/ run-time) знає, для якого типу об’єкта встановлено вказівник, і додає його розмір до покажчика. Це повне неправильне уявлення: воно знає лише адресу.
PJTraill

2
"Якщо voidвказівник, який вказує на розмір даних x, збільшується, як це зробити, щоб вказувати xбайти вперед?" Це не так. Чому люди, які мають такі запитання, не можуть перевірити їх перед тим, як запитати - ви знаєте, принаймні до мінімуму, де вони перевіряють, чи він насправді складається, чого це не відповідає. -1, не можу повірити, що це отримало +100 і -0.
підкреслюйте_d

Відповіді:


287

Остаточний висновок: арифметика на a void*є незаконною як для C, так і для C ++.

GCC дозволяє це як розширення, див. Арифметичні вказівки void- та функціональні покажчики (зверніть увагу, що цей розділ є частиною розділу "Розширення C" цього посібника). Clang та ICC, ймовірно, дозволяють void*арифметикувати для сумісності з GCC. Інші компілятори (наприклад, MSVC) забороняють вмикати арифметику void*, а GCC забороняє її, якщо -pedantic-errorsвказано прапор або якщо -Werror-pointer-arithвказано прапор (цей прапор корисний, якщо ваша база коду також повинна компілюватися з MSVC).

Стандарт C говорить

Цитати взяті з чернетки n1256.

В описі стандарту операції додавання зазначено:

6.5.6-2: Крім того, або обидва операнди мають арифметичний тип, або один операнд є вказівником на тип об'єкта, а інший має цілий тип.

Отже, тут питання void*полягає в тому, чи є вказівник на "тип об'єкта", або рівнозначно, чи void"тип об'єкта". Визначенням "тип об'єкта" є:

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

А стандарт визначає voidяк:

6.2.5-19: voidТип містить порожній набір значень; це незавершений тип, який неможливо виконати.

Оскільки voidце незавершений тип, це не тип об'єкта. Тому це не є дійсним операндом операції додавання.

Тому ви не можете виконувати арифметику вказівника на voidпокажчику.

Примітки

Спочатку вважалося, що void*арифметика дозволена через такі розділи стандарту С:

6.2.5-27: Вказівник на недійсність повинен мати ті ж вимоги щодо представлення та вирівнювання , що і вказівник на тип символів.

Однак,

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

Отже, це означає, що printf("%s", x)має те саме значення, чи xмає тип char*або void*, але це не означає, що ви можете робити арифметику на a void*.

Примітка редактора: Ця відповідь була відредагована для відображення остаточного висновку.


7
Зі стандарту C99: (6.5.6.2) Крім того, або обидва операнди мають арифметичний тип, або один операнд має вказівник на тип об'єкта, а інший має цілий тип. (6.2.5.19) Тип пустоти містить порожній набір значень; це незавершений тип, який неможливо виконати. Я думаю, що це дає зрозуміти, що void*арифметика вказівника не дозволена. GCC має розширення, яке дозволяє це зробити.
робота

1
якщо ви більше не вважаєте, що ваша відповідь корисна, ви можете просто видалити її.
caf

1
Ця відповідь була корисною, хоча виявилася помилковою, оскільки містить переконливі докази того, що недійсні покажчики не призначені для арифметики.
Бен Флінн

1
Це гарна відповідь, вона має правильний висновок та необхідні цитати, але люди, які прийшли до цього питання, прийшли до неправильного висновку, оскільки не прочитали до кінця відповіді. Я це відредагував, щоб зробити це більш очевидним.
Дітріх Епп

1
Clang та ICC не дозволяють void*арифметикувати (принаймні, за замовчуванням).
Сергій Подобрий

61

Арифметика вказівника заборонена у void*покажчиках.


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

1
@schot: Саме так. Більше того, арифметика вказівника визначається лише за вказівником на елемент об’єкта масиву і лише в тому випадку, якщо результатом операції буде вказівник на елемент того ж масиву або один минулий останній елемент цього масиву. Якщо ці умови не виконані, це невизначена поведінка. (Згідно стандарту C99 6.5.6.8)
робота

1
Мабуть, це не так з gcc 7.3.0. Компілятор приймає p + 1024, де p недійсний *. І результат такий же, як ((char *) p) + 1024
zzz777

@ zzz777 це розширення GCC, дивіться посилання у верхній частині голосової відповіді.
Руслан

19

киньте його на покажчик char, збільшуючи ваш покажчик вперед x байтами вперед.


4
Навіщо турбуватися? Просто наведіть його на потрібний тип - що завжди слід знати - і збільшити його за допомогою 1. Подавання char, збільшення xта повторне тлумачення нового значення як деякого іншого типу є безглуздою і невизначеною поведінкою.
підкреслити_

9
Якщо ви пишете свою функцію сортування, яка відповідно до man 3 qsortповинна бути void qsort(void *base, size_t nmemb, size_t size, [snip]), то у вас немає ніякого способу пізнати "правильний тип"
alisianoi

15

Стандарт C не допускає арифметики недійсних покажчиків. Тим НЕ менше, GNU C допускається з урахуванням розміру пустот IS 1.

C11 стандарт §6.2.5

Абзац - 19

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

Наступна програма прекрасно працює в компіляторі GCC.

#include<stdio.h>

int main()
{
    int arr[2] = {1, 2};
    void *ptr = &arr;
    ptr = ptr + sizeof(int);
    printf("%d\n", *(int *)ptr);
    return 0;
}

Можливо, інші компілятори генерують помилку.



8

Ви повинні закинути його на інший тип вказівника, перш ніж робити арифметику вказівника.


7

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

void *p = malloc(sizeof(char)*10);
p++; //compiler does how many where to pint the pointer after this increment operation

char * c = (char *)p;
c++;  // compiler will increment the c by 1, since size of char is 1 byte.

-1

Компілятор знає за типом ролях. Подано void *x:

  • x+1додає один байт x, покажчик переходить на байтx+1
  • (int*)x+1додає sizeof(int)байти, покажчик переходить на байтx + sizeof(int)
  • (float*)x+1адреси sizeof(float)байтів тощо.

Незважаючи на те, що перший елемент не є портативним і суперечить "Галатео" C / C ++, він все-таки є правильним на мові С, тобто означає, що він компілюватиме щось на більшості компіляторів, можливо, вимагаючи відповідного прапора (наприклад, -Wpointer-arith)


2
Althought the first item is not portable and is against the Galateo of C/C++Правда. it is nevertheless C-language-correctПомилковий. Це двічі! Арифметика вказівника на void *синтаксично незаконне, не повинна компілюватись і виробляє невизначене поведінку, якщо воно є. Якщо необережний програміст може змусити його складати, відключивши якесь попередження, це не привід.
підкреслити_

@underscore_d: Я думаю, що деякі компілятори використовували це як розширення, оскільки це набагато зручніше, ніж потрібно приводити, unsigned char*наприклад, додавати sizeofзначення покажчику.
supercat
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.