Каков современный, переносимый, безопасный эквивалент memcpy с проверками времени компиляции в C? - PullRequest
0 голосов
/ 04 июля 2019

Я смотрел на несколько кратких примеров кода, показывающих, как копировать float, не сталкиваясь с проблемами выравнивания памяти на конкретной аппаратной платформе.Я заметил, что он не использует sizeof():

uint8_t mybuffer[4];
float f;
memcpy(&f, mybuffer, 4)

Следующий вопрос - что использовать sizeof(), что приводит к частичному ответу Microsoft на memcpy_s , но это время выполнения подходит и требует проверки возврата.

Выходя за рамки приведенного мной фрагмента кода, для конкретного случая, когда размер источника и получателя можно правильно определить при время компиляции , есть ли компактная / однострочная (например, для краткости этого примера кода) эквивалент memcpy(), который гарантирует, что длины идентичны и завершает компиляцию с полезным сообщением, если нет?Тот, который избегает ловушек ARR01-C.Не применяйте оператор sizeof к указателю, если брать размер массива было бы неплохо.

Здесь есть некоторое совпадение с memcpy (), каким должно быть значение параметра size

Ответы [ 2 ]

1 голос
/ 04 июля 2019

Функция memcpy() отлично работает. Функции проверки границ C11 были введены как часть снижения вероятности дефектов переполнения буфера.

Документ N1967, Опыт работы в полевых условиях с Приложением K - Границы проверки интерфейсов , в разделе «Ненужное использование» есть следующее:

Распространенная ошибка, возникшая из-за того, что Microsoft осудила стандартные функции [DEPR] в целях расширения принятия API заключается в том, что каждый вызов стандартных функций обязательно небезопасно и должно быть заменено одним на «более безопасный» API. В следствии, Команды, ориентированные на безопасность, иногда наивно вступают в многомесячные проекты переписать свой рабочий код и покорно заменить все экземпляры «устаревшие» функции с соответствующими API. Это не только приводит к ненужному оттоку и повышает риск внедрения новых ошибок в правильный код, это также делает переписанный код менее эффективным.

Оператор sizeof - это оператор времени компиляции, который означает, что вычисление размера выполняется с помощью любой информации, доступной компилятору во время компиляции. Вот почему использование оператора может быть проблематичным и источником ошибок времени выполнения при использовании с указателями и массивами. Повышение уровня предупреждения компилятора и использование инструмента статического анализа кода может помочь найти эти проблемные области.

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

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

Так что, если у вас есть источник, такой как:

uint8_t mybuffer[4];
float f;
memcpy(&f, mybuffer, sizeof(mybuffer));

будет иметь правильное количество байтов, потому что массив mybuffer[4] вместе с его фактическим размером доступен для компилятора.

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

uint8_t mybuffer[4];
float f;
memcpy(&f, mybuffer, sizeof(f));

, где возникают проблемы с оператором sizeof, когда компилятор не может определить размер массива или размер объекта, адрес которого указан в указателе. Использование его с объявлением массива аргументов функции также небезопасно.

Если у вас есть функция int xxx (int a[5]), и в этой функции вы пытаетесь использовать оператор sizeof, чтобы получить размер массива в байтах, вы, скорее всего, получите размер int *.

1 голос
/ 04 июля 2019
  • Каков современный, переносимый, безопасный эквивалент memcpy с проверками времени компиляции в C?

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

    Если вы используете логику Microsoft, memcpy виноват в том, что некомпетентные программисты передают ему нулевые указатели. Но такие ошибки должны быть исправлены проверкой входных данных перед их передачей, а не изменением memcpy.

    memcpy_s следует считать устаревшим, и следует избегать всего, что связано с интерфейсом проверки границ C11, поскольку для него не существует поддержки компилятора. Опасные компиляторы, такие как Visual Studio, также сделали его непереносимым, поскольку они даже не следуют стандарту C Приложения K, а придумывают свою собственную несовместимую версию. Я советую рассматривать каждую функцию, заканчивающуюся _s, как угрозу безопасности.

  • есть ли компактная / единственная строка (например, чтобы код в этом примере был коротким), эквивалентная memcpy (), которая гарантирует, что длины идентичны, и завершает компиляцию с полезным сообщением, если нет?

    Да. memcpy(&f, mybuffer, sizeof(f))

  • Тот, который избегает ловушек ARR01-C. Не применяйте оператор sizeof к указателю, если брать размер массива было бы неплохо

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

    Вы можете использовать sizeof внутри функций, если знаете, что делаете. Каждая функция, принимающая массив в качестве параметра, также принимает размер. Используйте этот размер. Так что, если у вас есть это: void foo (size_t n, float array[n]);, тогда вы можете просто выполнить sizeof(float[n]) внутри этой функции.

    В качестве альтернативы есть еще одна неясная версия, иногда продвигаемая стандартами безопасности. Если вы измените функцию на использование указателя массива, void foo (size_t n, float(*array)[n]), вы можете сделать sizeof(*array) внутри этой функции. Мы также можем использовать указатели массива для повышения безопасности типов:

    void foo (float(*array)[5]);
    ...
    
    float arr[4];
    foo(&arr); // compiler error
    

    Недостатки: гораздо более сложный синтаксис, и мы теряем способность const исправлять параметры.

...