Поняття недійсного покажчика в програмуванні на С


129

Чи можна знецінити пустоту покажчика без кастингу типу на мові програмування C?

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

наприклад:

void abc(void *a, int b)
{
   if(b==1)
      printf("%d",*(int*)a);     // If integer pointer is received
   else if(b==2)
      printf("%c",*(char*)a);     // If character pointer is received
   else if(b==3)
      printf("%f",*(float*)a);     // If float pointer is received
}

Я хочу зробити цю функцію загальною без використання тверджень if-else - чи це можливо?

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

Також, чи можлива арифметика вказівників з недійсними покажчиками?

Відповіді:


97

Чи можна знецінити пустотілий покажчик без кастингу на мові програмування на C ...

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

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

Ви не можете просто знеструмити його портативно, оскільки він може бути неправильно вирівняний. Це може бути проблемою в деяких архітектурах, таких як ARM, де вказівник на тип даних повинен бути вирівняний на межі розміру типу даних (наприклад, вказівник на 32-бітове ціле число повинен бути вирівняний на 4-байтовій межі, щоб бути відхиленою).

Наприклад, читання uint16_tз void*:

/* may receive wrong value if ptr is not 2-byte aligned */
uint16_t value = *(uint16_t*)ptr;
/* portable way of reading a little-endian value */
uint16_t value = *(uint8_t*)ptr
                | ((*((uint8_t*)ptr+1))<<8);

Крім того, можлива арифметика вказівників з пустими вказівниками ...

Арифметика вказівника неможлива за покажчиками voidчерез відсутність конкретного значення під вказівником, а отже, і розміру.

void* p = ...
void *p2 = p + 1; /* what exactly is the size of void?? */

2
Якщо я не згадую неправильно, ви можете сміливо скасувати пустоту * двома способами. Кастинг на char * завжди прийнятний, і якщо ви знаєте оригінальний тип, на який він вказує, ви можете передати цей тип. Пустота * втратила інформацію про тип, тому її доведеться зберігати в іншому місці.
Ден Олсон

1
Так, ви завжди можете зневажати, якщо ви переходите на інший тип, але ви не можете зробити те ж саме, якщо ви не граєте. Коротше кажучи, компілятор не буде знати, які інструкції зі збирання використовувати для математичних операцій, поки ви не викладете їх.
Захарій Хамм

20
Я думаю, що GCC трактує арифметику за void *покажчиками так само char *, як це , але це не стандартно, і ви не повинні покладатися на неї.
ефеміент

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

2
@doliver Я мав на увазі №2 ("ми не можемо довіряти користувачеві знати тип аргументів, які вони передали функції"). Але переглянувши свою відповідь 6 років тому (так, чи справді це було так довго?) Я бачу, що ви маєте на увазі, це не завжди так: коли початковий вказівник вказував на потрібний тип, він би вже був вирівняний, так що було б безпечно подавати без проблем вирівнювання.
Олексій Б

31

В C void *може бути перетворений вказівник на об'єкт іншого типу без явного виступу:

void abc(void *a, int b)
{
    int *test = a;
    /* ... */

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

Ви не можете знехтувати a void * перетворивши його на інший тип вказівника, оскільки перенаправлення покажчика отримує значення об'єкта, що вказує. Голий voidне є допустимим типом так derefencing аvoid * НЕ представляється можливим.

Арифметика вказівника - це про зміну знаків вказівника на кратні sizeofоб'єкти, що вказують у напрямку. Знову ж таки, оскільки voidце не справжній тип, sizeof(void)не має значення, тому арифметика вказівника не діє на void *. (Деякі реалізації дозволяють це, використовуючи еквівалентну арифметику вказівника для char *.)


1
@CharlesBailey У своєму коді ви пишете void abc(void *a, int b). Але чому б не використати void abc(int *a, int b), оскільки у своїй функції ви врешті визначите int *test = a;, це дозволить зберегти одну змінну, і я не бачу інших переваг, визначаючи іншу змінну. Я бачу багато кодів, написаних таким чином, використовуючи void *як параметри, і передаваючи змінні згодом у функції. Але оскільки ця функція написана автором, тож він повинен знати використання змінної, отже, навіщо використовувати void *? Спасибі
Лев Лай

15

Ви повинні знати, що в C, на відміну від Java або C #, абсолютно немає можливості успішно "відгадати" тип об'єкта, на який void*вказує вказівник. Щось подібного getClass()просто не існує, оскільки цієї інформації ніде не знайти. З цієї причини тип "загального", який ви шукаєте, завжди має явну метеоформацію, як, наприклад, int bу вашому прикладі або рядок формату в printfсімействі функцій.


7

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


6

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

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

main()
{
    int *p;

    void *vp;

    vp=p;
} 

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

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

#include<stdio.h>

void absolute_value ( void *j) // works if used float, obviously it must work but thats not my interest here.
{
    if ( *j < 0 )
        *j = *j * (-1);

}

int main()
{
    int i = 40;
    float f = -40;
    printf("print intiger i = %d \n",i);
    printf("print float f = %f \n",f);
    absolute_value(&i);
    absolute_value(&f);
    printf("print intiger i = %d \n",i);
    printf("print float f = %f \n",f);
    return 0;
}   

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

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

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

void main()

{

    int a=10;

    float b=35.75;

    void *ptr; // Declaring a void pointer

    ptr=&a; // Assigning address of integer to void pointer.

    printf("The value of integer variable is= %d",*( (int*) ptr) );// (int*)ptr - is used for type casting. Where as *((int*)ptr) dereferences the typecasted void pointer variable.

    ptr=&b; // Assigning address of float to void pointer.

    printf("The value of float variable is= %f",*( (float*) ptr) );

}

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

void funct(void *a, int z)
{
    if(z==1)
    printf("%d",*(int*)a); // If user inputs 1, then he means the data is an integer and type casting is done accordingly.
    else if(z==2)
    printf("%c",*(char*)a); // Typecasting for character pointer.
    else if(z==3)
    printf("%f",*(float*)a); // Typecasting for float pointer
}

Ще один важливий момент, про який слід пам’ятати про покажчики недійсності, - це те, що арифметику вказівника не можна виконувати в недійсному покажчику.

void *ptr;

int a;

ptr=&a;

ptr++; // This statement is invalid and will result in an error because 'ptr' is a void pointer variable.

Тож тепер я зрозумів, у чому моя помилка. Я те саме виправляю.

Список літератури:

http://www.antoarts.com/void-pointers-in-c/

http://www.circuitstoday.com/void-pointers-in-c .

Новий код, як показано нижче.


#include<stdio.h>
#define INT 1
#define FLOAT 2

void absolute_value ( void *j, int *n)
{
    if ( *n == INT) {
        if ( *((int*)j) < 0 )
            *((int*)j) = *((int*)j) * (-1);
    }
    if ( *n == FLOAT ) {
        if ( *((float*)j) < 0 )
            *((float*)j) = *((float*)j) * (-1);
    }
}


int main()
{
    int i = 0,n=0;
    float f = 0;
    printf("Press 1 to enter integer or 2 got float then enter the value to get absolute value\n");
    scanf("%d",&n);
    printf("\n");
    if( n == 1) {
        scanf("%d",&i);
        printf("value entered before absolute function exec = %d \n",i);
        absolute_value(&i,&n);
        printf("value entered after absolute function exec = %d \n",i);
    }
    if( n == 2) {
        scanf("%f",&f);
        printf("value entered before absolute function exec = %f \n",f);
        absolute_value(&f,&n);
        printf("value entered after absolute function exec = %f \n",f);
    }
    else
    printf("unknown entry try again\n");
    return 0;
}   

Дякую,


2

Ні, це неможливо. Який тип повинен мати значення відновлення?


2
void abc(void *a, int b) {
  char *format[] = {"%d", "%c", "%f"};
  printf(format[b-1], a);
}

Чи можлива ця програма .... Будь ласка, перевірте, чи це можливо в програмуванні на C ...
AGeek

2
Так, це можливо (хоча код небезпечний без перевірки діапазону на b). Printf приймає змінну кількість аргументів, а просто натискає на стек як будь-який вказівник (без будь-якої інформації про тип) і вискакує всередині функції printf, використовуючи макроси va_arg, використовуючи інформацію з рядка формату.
hlovdal

@SiegeX: опишіть, що не є портативним, а що не можна визначити.
Готьє

@Gauthier, що передає змінну, яка містить специфікатори формату, не є переносним і потенційно не визначеним. Якщо ви хочете зробити щось подібне, то подивіться на аромат 'проти' printf. Ця відповідь є хорошим прикладом для цього.
SiegeX

На практиці я б, ймовірно, замінив int bперерахунок, але внесення будь-яких пізніх змін до цього перерахунку порушить цю функцію.
Джек Стаут

1

Я хочу зробити цю функцію загальною, не використовуючи ifs; Це можливо?

Єдиний простий спосіб, який я бачу, - це використовувати перевантаження .. що недоступно в ланцюзі програмування С AFAIK.

Чи вважали ви програмою C ++ для своєї програми? Або є якесь обмеження, яке забороняє його використання?


Гаразд, це соромно. З моєї точки зору, я не бачу рішень тоді. Насправді це те саме, що printf () & cout: два різні способи реалізації друку. printf () використовувати оператор if () для розшифровки рядка формату (я думаю, чи щось подібне), тоді як для оператора cout case << є перевантаженою функцією
yves Baumes

1

Ось короткий покажчик на voidпокажчики: https://www.learncpp.com/cpp-tutorial/613-void-pointers/

6.13 - Порожні покажчики

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

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

Несправність вказівника

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

Припускаючи , що пам'ять апарату являєшся байтами-адресації і не вимагає вирівняних доступів, найбільш загальний і атомний (ближче до подання рівня машини) способом інтерпретації void*є як покажчик на-а-байти, uint8_t*. Кастинг void*до uint8_t*дозволить вам, наприклад, роздрукувати перший 1/2/4/8 / , проте багатьом ви-бажання байтів , починаючи з цієї адреси, але ви не можете зробити багато чого іншого.

uint8_t* byte_p = (uint8_t*)p;
for (uint8_t* i = byte_p; i < byte_p + 8; i++) {
  printf("%x ",*i);
}

0

Ви можете легко роздрукувати недійсний принтер

int p=15;
void *q;
q=&p;
printf("%d",*((int*)q));

1
Хто гарантує, що voidмає той же розмір, що і int?
Ant_222

Це технічно не друкує voidпокажчик, це друк за intадресою пам'яті, яку містить вказівник (що в цьому випадку було б значенням p).
Marionumber1

0

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


0

void pointer - це загальний покажчик. Адреса будь-якого типу даних будь-якої змінної може бути присвоєна покажчику пустоти.

int a = 10;
float b = 3.14;
void *ptr;
ptr = &a;
printf( "data is %d " , *((int *)ptr)); 
//(int *)ptr used for typecasting dereferencing as int
ptr = &b;
printf( "data is %f " , *((float *)ptr));
//(float *)ptr used for typecasting dereferencing as float

0

Ви не можете знецінювати покажчик, не вказуючи його тип, оскільки різні типи даних матимуть різний розмір у пам'яті, тобто int має 4 байти, а знак - 1 байт.


-1

Принципово в C "типи" - це спосіб інтерпретувати байти в пам'яті. Наприклад, що таке наступний код

struct Point {
  int x;
  int y;
};

int main() {
  struct Point p;
  p.x = 0;
  p.y = 0;
}

Каже "Коли я запускаю main, я хочу виділити 4 (розмір цілого числа) + 4 (розмір цілого числа) = 8 (загальний байт) пам'яті. Коли я записую" .x "у вигляді значення, що має значення з міткою типу Вкажіть час компіляції, отримайте дані з місця пам'яті вказівника плюс чотири байти. Надайте значення повернення мітці часу компіляції "int." "

Всередині комп'ютера під час роботи ваша структура "Point" виглядає так:

00000000 00000000 00000000 00000000 00000000 00000000 00000000

Ось як void*може виглядати ваш тип даних: (якщо припустити 32-розрядний комп'ютер)

10001010 11111001 00010010 11000101

Це не відповідає на запитання
ММ

-2

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

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