Есть ли недостатки в передаче структур по значению в C, а не в передаче указателя?
Если структура велика, очевидно, что существует аспект производительного копирования большого количества данных, но для структуры меньшего размера она должна быть в основном такой же, как и передача нескольких значений в функцию.
Это может быть даже более интересно, когда используется в качестве возвращаемых значений. C имеет только одно возвращаемое значение из функций, но вам часто нужно несколько. Поэтому простое решение - поместить их в структуру и вернуть.
Есть ли причины для или против этого?
Поскольку не всем понятно, о чем я здесь говорю, приведу простой пример.
Если вы программируете на C, вы рано или поздно начнете писать функции, которые выглядят так:
void examine_data(const char *ptr, size_t len)
{
...
}
char *p = ...;
size_t l = ...;
examine_data(p, l);
Это не проблема. Единственная проблема заключается в том, что вы должны договориться со своим коллегой, в каком порядке должны быть параметры, чтобы вы использовали одинаковое соглашение во всех функциях.
Но что происходит, когда вы хотите вернуть такую же информацию? Обычно вы получаете что-то вроде этого:
char *get_data(size_t *len);
{
...
*len = ...datalen...;
return ...data...;
}
size_t len;
char *p = get_data(&len);
Это прекрасно работает, но гораздо более проблематично. Возвращаемое значение является возвращаемым значением, за исключением того, что в этой реализации это не так. Из вышесказанного невозможно сказать, что функция get_data не может посмотреть, на что указывает len. И нет ничего, что заставляет компилятор проверять, что значение фактически возвращается через этот указатель. Так что в следующем месяце, когда кто-то другой изменяет код, не понимая его должным образом (потому что он не читал документацию?), Он ломается, никто не замечает, или он начинает аварийно падать.
Итак, решение, которое я предлагаю, это простая структура
struct blob { char *ptr; size_t len; }
Примеры можно переписать так:
void examine_data(const struct blob data)
{
... use data.tr and data.len ...
}
struct blob = { .ptr = ..., .len = ... };
examine_data(blob);
struct blob get_data(void);
{
...
return (struct blob){ .ptr = ...data..., .len = ...len... };
}
struct blob data = get_data();
По некоторым причинам, я думаю, что большинство людей инстинктивно заставляют exam_data брать указатель на структурный объект, но я не понимаю, почему. Он по-прежнему получает указатель и целое число, просто гораздо яснее, что они идут вместе. А в случае с get_data невозможно все испортить, как я описал ранее, так как для длины нет входного значения и должна быть возвращаемая длина.
источник
void examine data(const struct blob)
это неверно.gettimeofday
), вместо этого используют указатели, и люди берут это в качестве примера.Ответы:
Для небольших структур (например, точка, прямоугольник) передача по значению вполне приемлема. Но, кроме скорости, есть еще одна причина, почему вы должны быть осторожны при передаче / возврате больших структур по значению: пространство в стеке.
Большая часть программирования на C предназначена для встраиваемых систем, где объем памяти ограничен, а размеры стека могут измеряться в килобайтах или даже байтах ... Если вы передаете или возвращаете структуры по значению, копии этих структур будут размещены на стек, потенциально вызывая ситуацию, что этот сайт назван в честь ...
Если я вижу приложение, которое, похоже, использует слишком много стеков, структуры, переданные по значению, - это одна из вещей, которые я ищу в первую очередь.
источник
Одна из причин не делать этого, которая не была упомянута, состоит в том, что это может вызвать проблему, где бинарная совместимость имеет значение.
В зависимости от используемого компилятора структуры могут передаваться через стек или регистры в зависимости от параметров / реализации компилятора.
Смотрите: http://gcc.gnu.org/onlinedocs/gcc/Code-Gen-Options.html
Если два компилятора не согласны, все может взорваться. Само собой разумеется, что основными причинами, по которым этого не делается, являются потребление стека и производительность.
источник
int &bar() { int f; int &j(f); return j;};
Чтобы действительно ответить на этот вопрос, нужно углубиться в землю собрания:
(В следующем примере используется gcc для x86_64. Любой желающий может добавить другие архитектуры, такие как MSVC, ARM и т. Д.)
Давайте иметь наш пример программы:
Скомпилируйте его с полной оптимизацией
Посмотрите на сборку:
Вот что мы получаем:
За исключением
nopl
колодок,give_two_doubles()
имеет 27 байтов, аgive_point()
имеет 29 байтов. С другой стороны,give_point()
дает на одну инструкцию меньше, чемgive_two_doubles()
Что интересно, мы заметили, что компилятор смог оптимизировать
mov
для более быстрых вариантов SSE2movapd
иmovsd
. Кроме того,give_two_doubles()
фактически перемещает данные из памяти, что замедляет процесс.По-видимому, большая часть этого может быть неприменима во встроенных средах (где игровое поле для C в настоящее время большую часть времени). Я не мастер сборки, поэтому любые комментарии приветствуются!
источник
const
все время), и я обнаружил, что при копировании с передачей по значению не наблюдается большого снижения производительности (если не выигрыша) вопреки тому, что многие могут поверить.Простое решение будет возвращать код ошибки в качестве возвращаемого значения и все остальное в качестве параметра в функции.
Этот параметр, конечно, может быть структурой, но не вижу каких-либо особых преимуществ, передавая это по значению, просто отправил указатель.
Передавать структуру по значению опасно, нужно быть очень осторожным с тем, что вы передаете, помните, что в C нет конструктора копирования, если один из параметров структуры является указателем, значение указателя будет скопировано, что может быть очень запутанным и трудным для понимания. поддерживать.
Просто чтобы завершить ответ (полная благодарность Родди ), использование стека является еще одной причиной, по которой структура не передается по значению, поверьте мне, отладка переполнения стека является реальной PITA.
Повторите комментарий:
Передача struct по указателю означает, что какая-то сущность владеет этим объектом и полностью знает, что и когда следует выпустить. Передача структуры по значению создает скрытые ссылки на внутренние данные структуры (указатели на другие структуры и т. Д.), При этом сложно поддерживать (возможно, но почему?).
источник
Люди, о которых здесь забыли упомянуть (или я упустил из виду), это то, что структуры обычно имеют отступы!
Каждый символ равен 1 байту, каждый короткий - 2 байта. Насколько велика структура? Нет, это не 6 байтов. По крайней мере, в более распространенных системах. На большинстве систем это будет 8. Проблема в том, что выравнивание не является постоянным, оно зависит от системы, поэтому одна и та же структура будет иметь разное выравнивание и разные размеры в разных системах.
Мало того, что заполнение еще больше поглотит ваш стек, оно также добавляет неопределенность в невозможности заранее предсказать заполнение, если только вы не знаете, как работает ваша система, а затем посмотрите на каждую структуру, имеющуюся в вашем приложении, и рассчитаете размер для этого. Передача указателя занимает предсказуемое количество места - нет никакой неопределенности. Размер указателя известен системе, он всегда равен, независимо от того, как выглядит структура, а размеры указателя всегда выбираются таким образом, чтобы они были выровнены и не нуждались в заполнении.
источник
Я думаю, что ваш вопрос подвел итог довольно хорошо.
Еще одно преимущество передачи структур по значению заключается в явном владении памятью. Не удивительно, что структура находится в куче и кто несет ответственность за ее освобождение.
источник
Я бы сказал, что передача (не слишком большие) структуры по значению, как в качестве параметров, так и в качестве возвращаемых значений, является совершенно законной техникой. Конечно, нужно позаботиться о том, чтобы структура была либо POD-типом, либо семантика копирования задана правильно.
Обновление: Извините, у меня была кепка C ++. Я вспоминаю время, когда было недопустимо возвращать структуру из функции в C, но с тех пор это, вероятно, изменилось. Я бы все еще сказал, что это верно, если все компиляторы, которые вы собираетесь использовать, поддерживают эту практику.
источник
Вот то, что никто не упомянул:
Члены a
const struct
естьconst
, но если этот член является указателем (напримерchar *
), он становитсяchar *const
скорее, чемconst char *
мы действительно хотим. Конечно, можно предположить, чтоconst
это документация намерений, и что любой, кто нарушает это, пишет плохой код (которым они являются), но этого недостаточно для некоторых (особенно для тех, кто только что провел четыре часа, выслеживая причину аварии).Альтернативой может быть создание
struct const_blob { const char *c; size_t l }
и использование этого, но это довольно грязно - оно сталкивается с той же проблемой со схемой именования, что и у меня сtypedef
указателями. Таким образом, большинство людей придерживаются только двух параметров (или, более вероятно, для этого случая, используя библиотеку строк).источник
struct const_blob
решением состоит в том, что даже еслиconst_blob
элементы имеют отличияblob
только от "косвенной константности", типыstruct blob*
для astruct const_blob*
будут считаться различными в целях строгого правила псевдонимов. Следовательно, если код преобразует ablob*
в aconst_blob*
, любая последующая запись в нижележащую структуру с использованием одного типа автоматически отключит любые существующие указатели другого типа, так что любое использование вызовет неопределенное поведение (которое обычно может быть безвредным, но может быть смертельным) ,На странице 150 руководства по сборке ПК по адресу http://www.drpaulcarter.com/pcasm/ содержится четкое объяснение того, как C позволяет функции возвращать структуру:
Я использую следующий код C, чтобы проверить вышеупомянутое утверждение:
Используйте «gcc -S» для генерации сборки для этого фрагмента кода C:
Стек перед вызовом create:
Стек сразу после вызова create:
источник
Я просто хочу указать на одно преимущество передачи ваших структур по значению в том, что оптимизирующий компилятор может лучше оптимизировать ваш код.
источник