Я пишу простую утилиту на 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 байта).
Да, это хорошая идея. Вы не знаете, что происходит где-то еще на вашем компьютере, какие другие процессы могли использовать (виртуальную) память.
..и передать достаточно информации, чтобы идентифицировать источник.
Кроме того, никогда не отказывайтесь от хороших привычек. Если вы начнете делать небольшие простые ярлыки, то вам будет проще сделать это в другом месте, где это действительно может иметь значение.
Да, это всего лишь один шаг от написания кода, который падает, когда пользователь чихает. Вам нужен надежный код.
@WeatherVane and pass enough information to identify the source
- Я не профессиональный программист, поэтому не знаю стандартных практик. Если я хочу идентифицировать источник, должен ли я придерживаться одного соглашения (например, всегда называть функцию, вызвавшую ошибку), или я могу быть гибким в именовании (например, назвать то, что вызывает у меня мысли о точном источнике, может не быть). имя исходной функции или переменной). Я хочу знать, как профессионалы делают это нейминг.
Вы можете передать аргумент для идентификации вызывающего абонента. Возможно, как уникальное целое число (хотя это сложно поддерживать) или имя __func__
, которое разрешается в строку. Например, объявить/определить void *my_malloc(size_t size, const char *func);
, а затем вызвать как arg = my_malloc(sizeof(struct arguments), __func__);
... вы можете передать что угодно своей обертке, которая направит вас к источнику ошибки - это ваше решение. В предыдущем комментарии у вас могло быть более одного malloc
в одной и той же функции.
Добавление имени функции или имени исходного файла, содержащего неудавшийся вызов, бесполезно. Ни одна из них не является трассировкой стека. Если ваш процесс потребляет слишком много памяти, вы хотите определить, какой компонент делает это с помощью других инструментов.
@WilliamPursell Справедливо отметить, что обычно существуют другие инструменты, но не всегда или не после развертывания. Следовательно, заключая эту информацию (имя функции, ...) в макрос, код может выборочно использовать или не использовать такие служебные данные с помощью простого изменения макроса, а не большого изменения базы кода.
Это «хорошая практика» / «это нормально», если я игнорирую проверку ошибок только по той причине, что я не выделяю памяти слишком много памяти
Плохая практика не проверять вызовы функций, которые сообщают об ошибках, за исключением тех случаев, когда вам все равно, был ли вызов успешным. И вам всегда важно, удастся ли 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-ReinstateMonica. Я соответственно обновил код примера.
Cinverse, обратите внимание на то, что мы с Джоном только что продемонстрировали здесь. Краеугольное улучшение, которое потребовало изменений только в одной небольшой функции. Рассмотрим большую программу, в которой было много malloc()
повсюду, которые должны были иметь дело с этим и необходимыми большими изменениями. Так приятно иметь malloc()
в одном месте.
@chux-ReinstateMonica Да, понял, спасибо. Кроме того, я думаю, было бы хорошо, если бы значение size
упоминалось в сообщении об ошибке, так как это был бы еще один шаг вперед, чтобы точно определить, какой вызов вызвал ошибку. если значение size
является каким-то мусорным значением, то мы можем определить источник, проверив, какой код может сделать size
таким значением.
Если вам это кажется хорошим, @Cinverse, тогда, во что бы то ни стало, продолжайте. Код, представленный в этом ответе, должен быть демонстративным, а не окончательным. Я применил исправление chux, предложенное как вопрос корректности для углового случая, но я считаю, что детали сообщения об ошибке зависят от того, чтобы такая функция соответствовала вашим конкретным целям.
Если у вас много объектов, вы можете сразу выделить память для всех:
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) Это работает нормально, если объекты имеют одинаковый размер. Если отличается, то в игру вступают проблемы с выравниванием.
Моя ошибка, p1 = (Obj1 *)pvc;
Создайте функции-оболочки распределения, чтобы упростить постоянную проверку успешности выделения.
Чтобы расширить @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 подпрограммы могут хранить статистику распределения, проверку свободного размера,...
Создайте функцию макроса/оболочки для повторяющегося кода.