Технически невозможно реализовать memcpy с нуля в стандарте C? - PullRequest
0 голосов
/ 24 января 2019

Говард Чу пишет :

В последней спецификации C невозможно написать "легальную" реализацию malloc или memcpy.

Это правильно?У меня сложилось впечатление, что в прошлом цель (по крайней мере) стандарта заключалась в том, чтобы что-то подобное работало:

void * memcpy(void * restrict destination, const void * restrict source, size_t nbytes)
{
    size_t i;
    unsigned char *dst = (unsigned char *) destination;
    const unsigned char *src = (const unsigned char *) source;

    for (i = 0; i < nbytes; i++)
        dst[i] = src[i];
    return destination;
}

Какие правила в последнем стандарте C здесь нарушаются?Или какая часть спецификации memcpy неправильно реализована этим кодом?

Ответы [ 2 ]

0 голосов
/ 24 января 2019

Для функции malloc, параграф 6.5 §6 проясняет, что невозможно написать совместимую и переносимую реализацию C:

Эффективный тип объекта для доступа кего сохраненным значением является объявленный тип объекта, если таковой имеется (87) ...

(ненормативное) примечание 87 гласит:

Выделенные объекты не имеют объявленного типа.

Единственный способ объявить объект без объявленного типа - это ... через функцию выделения, которая требуется для возврата такого объекта!Таким образом, внутри функции выделения у вас должно быть что-то , которое не может быть разрешено стандартом для установки зоны памяти без объявленного типа.

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

Но оставшаяся часть того же абзаца объясняет, что нет реальной проблемы в написании реализации memcpy (подчеркните мою):

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

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

ИМХО, разглагольствование Говарда Чу о том старом добреmemcpy использование, которое больше не действует (при условии sizeof(float) == sizeof(int)):

float f = 1.0;
int i;
memcpy(&i, &f, sizeof(int));         // valid: copy at byte level, but the value of i is undefined
print("Repr of %f is %x\n", i, i);   // UB: i cannot be accessed as a float
0 голосов
/ 24 января 2019

TL; DR
Это должно быть хорошо, если memcpy основан на наивной символьной копии.

И не оптимизирован для перемещения кусков размером самого большого выровненного типа, которые могут быть скопированы в одной инструкции. Последнее - то, как это делают стандартные реализации lib.


Это примерно такой сценарий:

void* my_int = malloc(sizeof *my_int);
int another_int = 1;

my_memcpy(my_int, &another_int, sizeof(int));

printf("%d", *(int*)my_int); // well-defined or strict aliasing violation?

Пояснение:

  • Данные, указанные на моем my_int, не имеют действующего типа.
  • Когда мы копируем данные в местоположение my_int, можно опасаться, что мы заставим эффективный тип стать unsigned char, поскольку именно это my_memcpy использует.
  • И затем, когда мы читаем эту ячейку памяти через int*. Будем ли мы нарушать строгий псевдоним?

Однако , ключ здесь - это особое исключение в правиле для эффективного типа, указанного в C17 6.5 / 6, выделено мое:

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

Поскольку мы копируем массив как символьный тип, эффективный тип, на который указывает my_int, станет типом объекта another_int, из которого было скопировано значение.

Так что все должно быть хорошо.

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

Примечательно, что это правило осталось тем же до C99, C11 и C17. Кто-то может возразить, что это очень плохое правило, которым злоупотребляют поставщики компиляторов, но это другая история.

...