Всегда ли безопасно приведение к указателям на указатели на void?

#include <stdio.h>

void swap(void *v[], int i, int j)
{
    void *tmp;

    tmp = v[i];
    v[i] = v[j];
    v[j] = tmp;
}

int main(void)
{
    char *s[] = {"one", "two"};
    printf("%s, %s\n", s[0], s[1]);
    swap(s, 0, 1);
    printf("%s, %s\n", s[0], s[1]);
    return 0;
}

Вывод:

one, two

two, one

Внимание! no compatible pointer casting, need void**, but char

Я использовал эту программу для моделирования функции подкачки в K&R, чтобы продемонстрировать использование указателя функции, и мой вопрос заключается в том, всегда ли безопасно приведение void pointer или есть ли способ заменить Это.


person pupu007    schedule 15.05.2013    source источник
comment
Я использую gcc std=c99 для его компиляции.   -  person pupu007    schedule 15.05.2013
comment
что на самом деле происходит, так это то, что указатели похожи на дескрипторы или адреса ... вы не меняете данные, вы меняете адрес на данные. Так что вполне безопасно.   -  person Aniket Inge    schedule 15.05.2013
comment
@Aniket: Нет, это не обязательно безопасно. Представьте себе систему, в которой размер char* отличается от размера int*.   -  person jamesdlin    schedule 15.05.2013


Ответы (3)


Нет, не обязательно безопасно передавать char** там, где ожидается void** (что на самом деле является параметром функции void*[]). Тот факт, что компилятор заставляет вас выполнять явное приведение типов, является намеком на это.

На практике, скорее всего, все будет хорошо. Однако, строго говоря, у вас обычно нет гарантии, что sizeof (T*) == sizeof (U*) для различных типов T и U. (Например, вы можете представить гипотетическую систему, в которой sizeof (int*) < sizeof (char*), потому что указатели на int выровнены и, следовательно, не нужно хранить младшие значащие биты.) Следовательно, ваша функция swap может индексировать массив v, используя неправильные смещения.

Также см. вопрос 4.9 в часто задаваемых вопросах comp.lang.c: Могу ли я указать формальный параметр введите void ** и сделайте что-нибудь подобное?

Чтобы безопасно вызвать swap, вы должны сделать что-то вроде:

void* temp[] = { &s[0], &s[1] };
swap(temp, 0, 1);

хотя это поменяло бы местами элементы temp, а не s.

Если вы создаете swap, в общем случае вы должны сделать так, чтобы такая функция принимала аргумент void* (вместо void**) и аргумент size_t, определяющий размер каждого элемента. Затем ваша функция может безопасно преобразовать void* в char* и поменять местами отдельные байты:

void swap(void* p, size_t elementSize, size_t i, size_t j)
{
    char* item1 = p;
    char* item2 = p;

    item1 += i * elementSize;
    item2 += j * elementSize;

    while (elementSize-- > 0) {
        char temp = *item1;
        *item1 = *item2;
        *item2 = temp;
        item1++;
        item2++;
    }
}

Изменить: также см. этот ответ StackOverflow на аналогичный вопрос.

person jamesdlin    schedule 15.05.2013
comment
блестящий ответ! спасибо, jamesdlin, но на моем компьютере все указатели кажутся эквивалентными, и я пошел по вашей ссылке, нашел [c-faq.com/null/machexamp.html], это то, что я хочу найти. - person pupu007; 15.05.2013
comment
@ pupu007 Даже если они равны, вы все равно вызываете неопределенное поведение в своей программе, если используете код из своего вопроса. - person this; 16.06.2014
comment
Есть гарантия того, что sizeof(char*)==sizeof(void*). - person n. 1.8e9-where's-my-share m.; 16.03.2015
comment
@n.'местоимения'm. Нет, нет. Не согласно ISO C (и этому ответу). У вас есть ссылка на это? - person Benjamin Crawford Ctrl-Alt-Tut; 14.09.2020
comment
@BenjaminCrawfordCtrl-Alt-Tut port70.net/~nsz/c/ c11/n1570.html#6.2.5p28 - person n. 1.8e9-where's-my-share m.; 14.09.2020
comment
Ого, сколько противоречивых вещей. Я слышал, что void* должен быть достаточно большим, чтобы содержать все типы указателей, поэтому имеет смысл, что он имеет тот же размер, что и указатель наименьшего адресуемого типа. - person Benjamin Crawford Ctrl-Alt-Tut; 14.09.2020

Вам нужно указать тип указателя в вызове подкачки. Измените его на swap ( ( void * )s, 0, 1 );

person unxnut    schedule 15.05.2013
comment
@Jens Gustedt: Изменяется ли ( void * ) с void * на другой тип? - person unxnut; 15.05.2013
comment
( void * )s сам по себе нет, но поскольку первым параметром swap является void**, неявное преобразование к нему происходит сразу после явного приведения и перед вызовом функции. - person Jens Gustedt; 15.05.2013
comment
@unxnut: Вы, по сути, используете char** =› void* =› void**. char** и void** не одного типа. - person jamesdlin; 15.05.2013
comment
Нет, код просто приводит char * =› void * и меняет местами указатели void *, фактически заменяя указатели char *. Он никоим образом не разыменовывает указатели void *. - person unxnut; 15.05.2013
comment
@unxnut: Нет, swap берет void**. И хотя он не разыменовывает тип void*, он разыменовывает void** (через v[i] и v[j]) и предполагает, что sizeof (void*) == sizeof (char*). - person jamesdlin; 15.05.2013

Чтобы избежать предупреждения, вызовите функцию следующим образом:

swap((void *) s, 0, 1);

Всегда безопасно использовать любой указатель как пустой указатель.

person Deepu    schedule 15.05.2013
comment
На самом деле вы приводите char** к void*, которое затем неявно приводится к void**. Преобразование T** в void** не обязательно безопасно. - person jamesdlin; 15.05.2013
comment
Этот ответ неверен. Преобразование в void* всегда безопасно, но неявное преобразование из void* в тип, отличный от исходного, — нет. - person Jens Gustedt; 15.05.2013
comment
@JensGustedt почему? некоторые компьютеры делают разные представления для указателей на разные типы. Поэтому void *[] может вызвать беспокойство, указывая на char *[] - person pupu007; 15.05.2013
comment
@ pupu007 функциональный параметр void*[] эквивалентен void**. Я не понимаю, как ты используешь беспокойство. - person Jens Gustedt; 15.05.2013