Проблема с проверкой ошибок для каждого вызова malloc?

Я пишу простую утилиту на C и пишу коды для вывода сообщений об ошибках в STDERR.

У меня есть структура, определенная как:

struct arguments
{
    FILE *source;
    int modifier_value;
    int filesize;
};

Я объявил указатель на вышеуказанную структуру и выделил для нее память:

struct arguments *arg = NULL;
arg = malloc(sizeof(struct arguments));
if (arg == NULL)
{
    fprintf(stderr, "error: malloc - %s\n", strerror(errno));  //I know i could use perror as well, but I like the convention of using fprintf() for both stdout, stderr, and other file streams, just for symmetry
    return EXIT_FAILURE;
}

Как вы можете видеть, я выделил только память, достаточную для хранения одного объекта типа struct arguments, и я все еще проверяю его на ошибки.

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

Является ли это «хорошей практикой» / «это нормально», если я игнорирую проверку ошибок только по той причине, что я не выделяю памяти слишком много памяти (я слышал что-то о пейджинге, и я думаю, что система объединяет много страниц, если я запрашиваю слишком много много памяти и шансы на ошибку в этом случае были бы высоки, но не для запроса памяти примерно на 64 байта).

Создайте функцию макроса/оболочки для повторяющегося кода.

Eugene Sh. 14.11.2022 21:00

Да, это хорошая идея. Вы не знаете, что происходит где-то еще на вашем компьютере, какие другие процессы могли использовать (виртуальную) память.

Some programmer dude 14.11.2022 21:01

..и передать достаточно информации, чтобы идентифицировать источник.

Weather Vane 14.11.2022 21:02

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

Some programmer dude 14.11.2022 21:02

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

Weather Vane 14.11.2022 21:04

@WeatherVane and pass enough information to identify the source - Я не профессиональный программист, поэтому не знаю стандартных практик. Если я хочу идентифицировать источник, должен ли я придерживаться одного соглашения (например, всегда называть функцию, вызвавшую ошибку), или я могу быть гибким в именовании (например, назвать то, что вызывает у меня мысли о точном источнике, может не быть). имя исходной функции или переменной). Я хочу знать, как профессионалы делают это нейминг.

Cinverse 14.11.2022 21:11

Вы можете передать аргумент для идентификации вызывающего абонента. Возможно, как уникальное целое число (хотя это сложно поддерживать) или имя __func__, которое разрешается в строку. Например, объявить/определить void *my_malloc(size_t size, const char *func);, а затем вызвать как arg = my_malloc(sizeof(struct arguments), __func__);

Weather Vane 14.11.2022 21:16

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

Weather Vane 14.11.2022 21:23

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

William Pursell 14.11.2022 21:41

@WilliamPursell Справедливо отметить, что обычно существуют другие инструменты, но не всегда или не после развертывания. Следовательно, заключая эту информацию (имя функции, ...) в макрос, код может выборочно использовать или не использовать такие служебные данные с помощью простого изменения макроса, а не большого изменения базы кода.

chux - Reinstate Monica 14.11.2022 22:09
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
10
55
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Ответ принят как подходящий

Это «хорошая практика» / «это нормально», если я игнорирую проверку ошибок только по той причине, что я не выделяю памяти слишком много памяти

Плохая практика не проверять вызовы функций, которые сообщают об ошибках, за исключением тех случаев, когда вам все равно, был ли вызов успешным. И вам всегда важно, удастся ли malloc(), иначе вам вообще не следует его вызывать. Вы не знаете, выделяете ли вы слишком много памяти, пока не проверите, успешны ли ваши malloc() вызовы.

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

Во-первых, используйте динамическое размещение только там, где оно действительно необходимо. У некоторых людей, похоже, есть идея, что им нужно динамическое размещение везде, где им нужен указатель на объект. Это абсолютно не так. Вам нужны указатели, если вы выполняете динамическое размещение, но вам не обязательно нужно динамическое размещение, когда вы используете указатели. Очень часто вместо статического или автоматического распределения используется оператор адреса (унарный &). Например:

{
    struct arguments arg = {0};
    init_arguments(&arg);
    do_something(&arg);
    // all done with arg
}

Вам нужно динамическое выделение только тогда, когда (i) вы не знаете во время компиляции, сколько памяти вам понадобится, или (ii) вам нужен объект, время жизни которого превышает завершение самого внутреннего блока, включающего его создание.

Когда вам действительно нужно динамическое размещение, вы можете уменьшить объем шаблонного кода, используя макрос или функцию-оболочку. То же самое относится и к выполнению других проверок успеха. Например, используйте такую ​​функцию вместо прямого использования malloc():

void *checked_malloc(size_t size) {
    void *result = malloc(size);

    if (result == NULL && size != 0) {
        fputs("error: malloc failed -- aborting...\n", stderr);
        abort();
    }
    return result;
}

Даже с C17 checked_malloc(0) может привести к abort(). ИМО, лучше просто вернуть NULL или изменить на if (!result && size > 0) {, чтобы справиться с этим надоедливым угловым случаем.

chux - Reinstate Monica 14.11.2022 21:26

Хороший вопрос, @chux-ReinstateMonica. Я соответственно обновил код примера.

John Bollinger 14.11.2022 21:29

Cinverse, обратите внимание на то, что мы с Джоном только что продемонстрировали здесь. Краеугольное улучшение, которое потребовало изменений только в одной небольшой функции. Рассмотрим большую программу, в которой было много malloc() повсюду, которые должны были иметь дело с этим и необходимыми большими изменениями. Так приятно иметь malloc() в одном месте.

chux - Reinstate Monica 14.11.2022 21:33

@chux-ReinstateMonica Да, понял, спасибо. Кроме того, я думаю, было бы хорошо, если бы значение size упоминалось в сообщении об ошибке, так как это был бы еще один шаг вперед, чтобы точно определить, какой вызов вызвал ошибку. если значение size является каким-то мусорным значением, то мы можем определить источник, проверив, какой код может сделать size таким значением.

Cinverse 14.11.2022 21:53

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

John Bollinger 14.11.2022 22:03

Если у вас много объектов, вы можете сразу выделить память для всех:

void *pvc;
pvc = malloc(how_many_for_all);

а затем последовательно объявить указатели на объекты следующим образом:

Obj1 *p1;
Obj2 *p2;
Obj3 *p3;
p1 = (Obj1)pvc;
p2 = (Obj2)(pvc + sizeof(Obj1));
p3 = (Obj3)(pvc + sizeof(Obj1) + sizeof(Obj2));

Это псевдокод скорее. Компилятор будет делать предупреждения, но это работает.

1) (Obj2)(pvc + sizeof(Obj1)) должно быть (Obj2*)((char *)pvc + sizeof(Obj1)) иначе сложение неверно. 2) Это работает нормально, если объекты имеют одинаковый размер. Если отличается, то в игру вступают проблемы с выравниванием.

chux - Reinstate Monica 14.11.2022 22:04

Моя ошибка, p1 = (Obj1 *)pvc;

Peter Irich 15.11.2022 18:56

Создайте функции-оболочки распределения, чтобы упростить постоянную проверку успешности выделения.

Чтобы расширить @Weather Vane идею передачи аргумента для идентификации вызывающего абонента, но сделать это в оболочке макроса.

my_malloc.h

#ifndef MY_MALLOC
#define MY_MALLOC(size) my_malloc((size), __FUNC__, __LINE__)
#include <stdlib.h>

// Return a valid allocation or don't return.
void *my_malloc(size_t size, const char *func, unsigned line);

#endif

my_malloc.c

#include "my_maloc.h"

#include <stdio.h>
#include <stdlib.h>

void *my_malloc(size_t size, const char *func, unsigned line) {
  if (size == 0) {
    return NULL;
  }
  void *ptr = malloc(size);
  if (ptr == NULL) {
    fprintf(stderr, "Failed to allocate %zu bytes at \"%s()\", line %u\n",
        size, func, line);
    exit(EXIT_FAILURE);
  }
  return ptr;
}

user_code.c

#include "my_malloc.h"

...
  // No need to explicitly pass the function name, line number
  arg = MY_MALLOC(sizeof *arg * n);
  // Error checking not needed.

  ...
  free(arg);

Я бы даже подумал о совпадении MY_FREE, где освобождение проходит в указателе и ожидаемом размере. Затем эти 2 подпрограммы могут хранить статистику распределения, проверку свободного размера,...

Другие вопросы по теме