2016-10-10 9 views
6

Я хотел бы написать общую функцию, чтобы определить, содержит ли массив указателей какой-либо произвольный тип NULL. Моя первая попытка была что-то вдоль этих линий:Написание общей функции для определения того, содержит ли массив указателей NULL

bool find_null (void *ptrs, size_t num_ptrs) { 
    void **array = ptrs; 
    size_t i; 
    for (i = 0; i < num_ptrs; ++i) { 
     if (array[i] == NULL) return true; 
    } 
    return false; 
} 

Было отмечено, что это может вызвать строгое нарушение сглаживания, так как массив указателей на Foo будет доступен как массив указателей на void, который не указано в списке в качестве одного из разрешенных способов объекта разрешается доступ в C.2011 § 6,5 ¶ 7.

Я мог бы переписать функцию, чтобы получить доступ к массиву указателей, как unsigned char * вместо этого, но я не знаю, как выполнить NULL проверка без нарушения строгого наложения. Может ли кто-нибудь предоставить действительную технику?

bool find_null (void *ptrs, size_t num_ptrs) { 
    unsigned char *array = ptrs; 
    void *p; 
    size_t i; 
    for (i = 0; i < num_ptrs; ++i) { 
     memcpy(&p, array + i * sizeof(p), sizeof(p)); 
     if (p == NULL) return true; 
     /* 
     * Above seems to still break strict aliasing. 
     * What should be done instead? 
     */ 
    } 
    return false; 
} 

Цель состоит в том, чтобы написать обобщенную функцию, которая будет работать так же, как специфическая функция типа. Другими словами, общая версия функции ниже:

bool find_null_Foo (Foo *array[], size_t num_ptrs) { 
    size_t i; 
    for (i = 0; i < num_ptrs; ++i) { 
     if (array[i] == NULL) return true; 
    } 
    return false; 
} 
+8

Это невозможно вообще. Нет требования, чтобы 'sizeof (void *)' был равен 'sizeof (Foo *)'.Они могут быть неравными на машинах, не адресуемых по байтам, где 'sizeof (void *)' немного больше, чем 'sizeof (Foo *)' из-за необходимости записи байта в слово, которое указывает указатель. –

+1

Вы хотите, чтобы код рассматривал только нулевой указатель со значением '(void *) NULL' или хотел бы обработать возможность того, что платформа имеет различные значения нулевых указателей? (Конечно, все нулевые указатели приравниваются друг к другу, хотя могут иметь разные кодировки.) – chux

+0

@RaymondChen: Спасибо, что принесли это, это действительная точка. Я отправил ответ, который должен учитывать это. Тем не менее, для такой машины я, вероятно, закончил бы ее создание 30-битным адресом (для 4-байтовых слов) или 61-битным адресом (для 8-байтовых слов) вместо того, чтобы пытаться обращаться с указателями разного размера. – jxh

ответ

1

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

bool find_null (const void *array, size_t num_ptrs, size_t ptr_size, 
     const void *null) { 
    const char (*ptr_array)[ptr_size] = array; 
    size_t i; 
    for (i = 0; i < num_ptrs; ++i) { 
     if (!memcmp(array[i], null, ptr_size)) return true; 
    } 
    return false; 
} 

Вы назвали бы, что вот так:

struct Foo; 

#define ARRAY_SIZE 53 
int main(void) { 
    struct Foo *my_array[ARRAY_SIZE] = { ... }; 
    struct Foo * const foo_null = (struct Foo *) 0; 
    if (find_null(my_array, ARRAY_SIZE, sizeof(*my_array), &foo_null)) { 
     puts("It contains NULL"); 
    } else { 
     puts("It does not contain NULL"); 
    } 
} 

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

Обратите также внимание на то, что это не имеет особого отношения к поиску нулевых указателей, поэтому на самом деле вы можете использовать его для поиска вашего указательного массива для любого значения указателя. Фактически, он даже не специфичен для массивов указателей - вы можете использовать его для поиска любого массива для любого значения, если байт-for-byte равен подходящему критерию соответствия (который он не для структур или союзы и, возможно, не для некоторых других типов).

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

+0

Нет гарантии, что вы можете сравнить нулевые указатели разных размеров, memcmp ,. На самом деле нет никакой гарантии, что это правильно. Sizeof (void *)> = size of (int *) ' – chqrlie

+0

@chqrlie. Вот почему представленная функция принимает указатель * на * значение нулевого указателя для сравнения с элементами массива и использует 'memcmp()' для выполнения сравнений. Я сравниваю указатели того же типа. –

+0

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

1

На основании Raymond's comment, кажется, что для общей функции потребуются дополнительные аргументы. Поскольку невозможно преобразовать представление указателей в void *, это необходимо сделать с помощью обратного вызова.

bool find_null_generic (const void *ptrs, size_t ptr_sz, size_t num_ptrs, 
         const void *(*convert)(const void *)) { 
    const unsigned char *array = ptrs; 
    size_t i; 
    for (i = 0; i < num_ptrs; ++i) { 
     if (convert(array + i * ptr_sz) == NULL) return true; 
    } 
    return false; 
} 

Для гипотетического массива указателей на Foo:

const void *convert_Foo (const void *data) { 
    const Foo *foo; 
    memcpy(&foo, data, sizeof(foo)); 
    return foo; 
} 

Foo *foo_array[N] = {...}; 
bool result = find_null_generic(foo_array, sizeof(Foo *), N, convert_Foo); 
1

Родовая функция не гарантируется, как ожидается, в системах, где указатели на разные типы могут иметь различные представления и/или размеры. К счастью, такие архитектуры сейчас довольно редки. Например, системы, совместимые с Posix, гарантируют использование одинакового представления и размера для всех типов указателей.

+1

К сожалению, нарушение строгой псевдонимы все еще может привести к тому, что компилятор генерирует неожиданные результаты оптимизации даже в тех системах, где размеры указателей одинаковы. – jxh

+0

@jxh: такое поведение настолько противоречиво, что его следует считать ошибкой. Интересно, как можно было бы сформулировать строгую алиасизацию, чтобы предотвратить такие далеко идущие последствия. – chqrlie

+0

@chqrlie: Простейшая формулировка должна заключаться в том, чтобы указать, что приведение указателя от типа «T *» к «U *» предоставит лицензию на использование этой цели или указателей, полученных из нее как «U *», до следующего раза, когда объект доступен через любой указатель, не полученный из результата вышеупомянутого приведения. Это не исключало бы многих полезных оптимизаций, но, вероятно, избегало бы использовать '-fno-strict-aliasing' с 90% программ, которые в настоящее время требуют этого. – supercat

 Смежные вопросы

  • Нет связанных вопросов^_^