2016-03-11 5 views
3

Связанных, но несколько отличается от, Do any compilers transfer effective type through memcpy/memmoveтипа агностика тетсра в C99

В C89, memcpy и memmove должны вести себя так, как будто источник и назначение доступны с использованием типов символов, копированием всех бит источник для адресата без учета типа копируемых данных.

C99 изменяет семантику так, что если объект с эффективным типом копируется в хранилище, у которого нет объявленного типа [обычно это память, полученная от malloc или другой такой функции], он создает объект в целевом хранилище, к которому можно получить доступ используя тип источника.

В следующем коде, например, было бы полностью определено поведение в C89 на всех платформах, где «unsigned int» и «unsigned long» имеют одно и то же 32-битное представление, но имели бы Undefined Behavior под C99.

#include <stdio.h> 
#include <string.h> 
void increment_32_bit_uint(void *p) 
{ 
    uint32_t temp; 
    memcpy(&temp, p, sizeof temp); 
    temp++; 
    memcpy(p, &temp, sizeof temp); 
} 
int main(void) 
{ 
    unsigned *ip = malloc(sizeof (unsigned)); 
    unsigned long *lp = malloc(sizeof (unsigned long)); 
    *ip = 3; *lp = 6; 
    increment_32_bit_uint(ip); 
    increment_32_bit_uint(lp);  
    printf("%u %lu", *ip, *lp); 
    return 0; 
} 

Согласно правилам C99, передавая выделенную память для функции «increment_32_bit_uint» сделает это установить эффективный тип для uint32_t, которая не может быть такого же типа, как и «без знака» и «без знака долго», даже если все три типы имеют одинаковое представление. Следовательно, компилятор может делать все, что ему нравится, с кодом, который читает это хранилище как тип, отличный от uint32_t, даже если этот тип имеет такое же представление.

Есть ли способ на C99 или C11 выполнить копию таким образом, чтобы компилятор мог генерировать эффективный код, но заставил компилятор обрабатывать адресат так, как если бы он содержал шаблон бит без эффективный тип [который можно было бы получить с помощью любого типа]?

+0

GCC компилирует пример с предупреждения не используя '-std = c99' или' -std = c11' после включения '' и '' xvan

+2

@xvan: Это конкретный компилятор (или даже каждый компилятор, который существует сегодня) что-то не означает, что Стандарт налагает какое-либо требование продолжать это делать.Существует несколько случаев, когда почти каждый существующий компилятор имел одинаковое поведение в течение десятилетий, не требуя от них Стандарта, пока некоторые авторы компилятора не решили, что им больше не нужно поддерживать эти случаи, поэтому тот факт, что код работает на всех сегодняшних компиляторы не означает, что он не вызывает UB. – supercat

+1

@xvan: Per N1570: «Если значение копируется в объект, не имеющий объявленного типа с использованием memcpy или memmove, или копируется как массив типа символа, тогда эффективный тип измененного объекта для этого доступа и для последующего доступа которые не изменяют значение, является эффективным типом объекта, из которого копируется значение, если оно есть. " Я не вижу оснований говорить, что эффективный тип не будет установлен в 'uint32_t', и это чтение, поскольку любой другой тип не будет вызывать Undefined Behavior. – supercat

ответ

0

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

uint32_t increment_32_bit_uint (const void* p) 
{ 
    u32_t result; 
    memcpy(&result, p, sizeof(result)); 
    result++; 
    return result; 
} 

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

x = increment_32_bit_uint(&x); 

Вообще, я не понимаю, как любые строгие сглаживания оптимизаций когда-либо будет полезно в реальных приложений, если они не рассматривают типы stdint.h как совместимые типы для их примитивных эквивалентов типа данных. В частности, он должен обрабатывать uint8_t в качестве символьного типа, или весь профессиональный низкоуровневый код C сломается.

То же самое для вашего случая здесь. Если компилятор знает, что unsigned int 32 бит, почему он решил вызвать проблемы с псевдонимом для пользователей uint32_t и наоборот? Вот как вы превращаете компилятор бесполезно.

+0

Хотя в этом примере я использовал один элемент данных, возникают большие проблемы с массивами (например, напишите функцию для уменьшения каждого элемента в массиве, который еще не равен нулю], поэтому использование возвращаемого значения функции не будет работать. Кроме того, если вы играете с онлайновыми компиляторами, например, gcc.godbolt.org, вы заметите, что хотя оба «int» и «long» равны 32 битам, если функция принимает как «int *», так и «long *», компилятор предположит, что два указателя не могут получить доступ тот же объект. Это не редкость для программы, которая имеет некоторые библиотеки, которые ожидают массив 'unsigned', некоторые из которых ожидают ... – supercat

+0

... массив' unsigned long', а некоторые, которые ожидают массив 'uint32_t', и для программа должна обмениваться данными между этими библиотеками. Существует также много случаев, когда программам может потребоваться работать с указателями на различные типы структур, которые имеют общую начальную последовательность; Однако сегодняшние компиляторы не допускают этого, если не используется «memcpy», и даже при использовании «memcpy» эффективные правила типа не гарантируют, что он будет работать. – supercat

+0

@supercat Тогда я полагаю, что единственным вариантом является использование объединения, с типом, пишущим в/из типа char. – Lundin