Для приведения массива указателей (например, *array[]), зачем использовать (void**) вместо (void*)

Следующий фрагмент кода взят из K&R Глава 5-11: Указатели на функции:

qsort((void**) lineptr, 0, nlines-1, 
   (int (*)(void *, void *)(numeric ? numcmp : strcmp));

Я могу скомпилировать/запустить код с помощью (void*), так почему вместо этого lineptr использует (void **)? Есть ли какие-то внутренние различия между двумя приведениями или это больше для удобочитаемости? Является ли (void *) приведением только к массиву, тогда как (void **) приводит как к массиву, так и к сохраненным указателям?

Я понимаю, что **lineptr эквивалентно *lineptr[], и причина приведения типа void заключается в том, чтобы осчастливить компилятор.


person Kevin    schedule 26.12.2014    source источник
comment
Здесь сортировка выполняется по данным, на которые указывают указатели, хранящиеся в массиве.   -  person haccks    schedule 26.12.2014
comment
Да, так применяется ли приведение к указателям с (void **), но приведение (void *) выполняется только к массиву?   -  person Kevin    schedule 27.12.2014
comment
В этом случае кастинг будет выполнен как void**.   -  person haccks    schedule 27.12.2014


Ответы (1)


Скорее всего это неправильно.

Пустота* — это указатель, указывающий на что-то. Мы не знаем, на что это указывает. Вы можете привести любой указатель к void*. Функция сравнения, которую вы передаете qsort, должна угадать, на какой фактический тип указывает void*. Если получится, то ладно. Если что-то пойдет не так, все пойдет не так — это жизнь и ответственность программиста.

void** — это указатель на массив void*. Здесь есть большая разница: хотя вы можете без проблем преобразовать, например, int* в void*, массив int* определенно не является массивом void*. int* и void* могут иметь разные размеры! Стандарт C довольно четко указывает, какие типы указателей имеют одинаковое представление:

  1. Любой вариант void* и char* (void** не является вариантом void*)
  2. Любые указатели на структуры
  3. Любые указатели на союзы
  4. Любые указатели на примитивные типы разной подписи (например, int, unsigned int).

Любые указатели, не входящие в одну и ту же группу, могут иметь разные представления, поэтому приведение может работать, но memcpy или чтение значения через указатель на другой тип указателя не работает.

person gnasher729    schedule 26.12.2014
comment
Когда вы говорите, что это, скорее всего, неверно, вы имеете в виду код, предоставленный K&R? - person Kevin; 27.12.2014
comment
Я читаю второе издание, найденное по ссылке. Кроме того, является ли приведение (void **) только приведением указателей в массиве? - person Kevin; 27.12.2014