2008-10-02 10 views
127

Есть ли недостатки для передачи структур по значению в C, а не для передачи указателя?Есть ли недостатки для передачи структур по значению в 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(); 

По какой-то причине, я думаю, что большинство людей инстинктивно делают examine_data принимать указатель на struct blob, но я не понимаю, почему. Он по-прежнему получает указатель и целое число, гораздо проще, что они идут вместе. И в случае get_data невозможно испортить то, что я описал ранее, поскольку для длины нет входного значения, и должна быть возвращенная длина.

+0

Для чего это необходимо, `void проверить данные (const struct blob)` неверно. – 2011-09-26 04:37:47

+0

Спасибо, изменил его, чтобы включить имя переменной. – dkagedal 2011-09-27 11:47:14

+1

«Нельзя сказать из вышеизложенного, что функция get_data не имеет права смотреть на то, что указывает точка. И нет ничего, что заставило компилятор проверить, действительно ли значение возвращается через этот указатель». - это не имеет никакого смысла для меня вообще (возможно, потому, что ваш пример является недопустимым кодом из-за последних двух строк, появляющихся вне функции); пожалуйста, вы можете уточнить? – 2013-04-11 10:36:46

ответ

10

Я бы сказал, что передаваемые (не слишком большие) структуры по значению, как параметры, так и возвращаемые значения, являются совершенно законной техникой. Нужно, конечно, позаботиться о том, чтобы структура была либо типом POD, либо семантикой копирования хорошо указаны.

Обновление: Извините, у меня была мыслящая крышка C++. Я помню время, когда в C не было законно возвращать структуру из функции, но с тех пор это, вероятно, изменилось. Я бы все же сказал, что это действительно так, пока все компиляторы, которые вы ожидаете использовать, поддерживают практику.

+0

Обратите внимание, что мой вопрос касался C, а не C++. – dkagedal 2008-10-02 11:30:14

+0

Действительно вернуть функцию struct из функции просто не полезно :) – Ilya 2008-10-02 11:45:27

+1

Мне нравится предложение llya использовать return как код ошибки и параметры для возврата данных из функции. – zooropa 2009-04-29 12:26:27

9

Я думаю, что ваш вопрос подвел итоги довольно хорошо.

Другим преимуществом передачи структур по значению является то, что владение памятью является явным. Не стоит задаваться вопросом, существует ли структура из кучи и кто несет ответственность за ее освобождение.

15

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

Только для завершения ответа (полный кредит Roddy) использование стека является другой причиной, по которой не проходит структура по значению, поверьте, что отладка переполнения стека является реальной PITA.

Replay на комментарий:

Передача по указателю-структуру, означающего, что некоторые компания имеет право собственности на этот объект и иметь полное представление о том, что и когда должен быть освобожден. Передача структуры по значению создает скрытые ссылки на внутренние данные структуры (указатели на другие структуры и т. Д.), При этом ее трудно поддерживать (возможно, но почему?).

161

Для небольших структур (например, точек, прямых), проходящих по значению, вполне приемлемо. Но, кроме скорости, есть еще одна причина, по которой вам следует осторожно передавать/возвращать большие структуры по значению: пространство стека.

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

Если я вижу приложение, которое, как представляется, имеет чрезмерное использование стека, структуры, переданные по значению, являются одной из вещей, которые я ищу в первую очередь.

8

Одна вещь, о которой люди забыли упомянуть до сих пор (или я упустил это), состоит в том, что у структур обычно есть прокладка!

struct { 
    short a; 
    char b; 
    short c; 
    char d; 
} 

Каждый символ 1 байт, каждый короткий - 2 байта. Насколько велика структура? Нет, это не 6 байтов. По крайней мере, не в более часто используемых системах. В большинстве систем это будет 8. Проблема в том, что выравнивание не является постоянным, оно зависит от системы, поэтому одна и та же структура будет иметь разное выравнивание и разные размеры для разных систем.

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

59

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

В зависимости от используемого компилятора, структуры могут быть переданы через стек или регистры в зависимости от опций компилятора/реализации

См: http://gcc.gnu.org/onlinedocs/gcc/Code-Gen-Options.html

-fpcc-структура обратного

-freg -струкция-возврат

Если два компилятора не согласны, все может взорваться. Излишне говорить, что основными причинами, по которым это не делается, проиллюстрировано на примере потребления стека и производительности.

18

Чтобы действительно ответить на этот вопрос, нужно копать глубоко в сборочном земли:

(. Следующий пример использует GCC на x86_64 Любой приветствуется добавление других архитектур, как MSVC, ARM и т.д.)

Давайте наш пример программы:

// foo.c 

typedef struct 
{ 
    double x, y; 
} point; 

void give_two_doubles(double * x, double * y) 
{ 
    *x = 1.0; 
    *y = 2.0; 
} 

point give_point() 
{ 
    point a = {1.0, 2.0}; 
    return a; 
} 

int main() 
{ 
    return 0; 
} 

Собирать с полной оптимизацией

gcc -Wall -O3 foo.c -o foo 

Посмотрите на сборку:

objdump -d foo | vim - 

Это то, что мы получаем:

0000000000400480 <give_two_doubles>: 
    400480: 48 ba 00 00 00 00 00 mov $0x3ff0000000000000,%rdx 
    400487: 00 f0 3f 
    40048a: 48 b8 00 00 00 00 00 mov $0x4000000000000000,%rax 
    400491: 00 00 40 
    400494: 48 89 17    mov %rdx,(%rdi) 
    400497: 48 89 06    mov %rax,(%rsi) 
    40049a: c3      retq 
    40049b: 0f 1f 44 00 00   nopl 0x0(%rax,%rax,1) 

00000000004004a0 <give_point>: 
    4004a0: 66 0f 28 05 28 01 00 movapd 0x128(%rip),%xmm0 
    4004a7: 00 
    4004a8: 66 0f 29 44 24 e8  movapd %xmm0,-0x18(%rsp) 
    4004ae: f2 0f 10 05 12 01 00 movsd 0x112(%rip),%xmm0 
    4004b5: 00 
    4004b6: f2 0f 10 4c 24 f0  movsd -0x10(%rsp),%xmm1 
    4004bc: c3      retq 
    4004bd: 0f 1f 00    nopl (%rax) 

Исключая nopl колодки, give_two_doubles() имеет 27 байт, а give_point() имеет 29 байт. С другой стороны, give_point() дает одну меньше, чем инструкция give_two_doubles()

Что интересно, что мы замечаем, что компилятор смог оптимизировать mov в более быстрый SSE2 варианты movapd и movsd. Кроме того, give_two_doubles() действительно перемещает данные из памяти и выходит из нее, что замедляет работу.

По-видимому, большая часть этого может быть неприменима во встроенных средах (где игровое поле для C в большинстве случаев находится сейчас). Я не мастер сборки, поэтому любые комментарии будут приветствоваться!

5

Page 150 Учебное пособие PC Ассамблеи по http://www.drpaulcarter.com/pcasm/ имеет четкое объяснение о том, как C позволяет функцию возвращать-структуру:

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

Я использую следующий код C, чтобы проверить приведенное выше утверждение:

struct person { 
    int no; 
    int age; 
}; 

struct person create() { 
    struct person jingguo = { .no = 1, .age = 2}; 
    return jingguo; 
} 

int main(int argc, const char *argv[]) { 
    struct person result; 
    result = create(); 
    return 0; 
} 

Используйте "НКУ -S" для создания сборки для этого куска кода C:

.file "foo.c" 
    .text 
.globl create 
    .type create, @function 
create: 
    pushl %ebp 
    movl %esp, %ebp 
    subl $16, %esp 
    movl 8(%ebp), %ecx 
    movl $1, -8(%ebp) 
    movl $2, -4(%ebp) 
    movl -8(%ebp), %eax 
    movl -4(%ebp), %edx 
    movl %eax, (%ecx) 
    movl %edx, 4(%ecx) 
    movl %ecx, %eax 
    leave 
    ret $4 
    .size create, .-create 
.globl main 
    .type main, @function 
main: 
    pushl %ebp 
    movl %esp, %ebp 
    subl $20, %esp 
    leal -8(%ebp), %eax 
    movl %eax, (%esp) 
    call create 
    subl $4, %esp 
    movl $0, %eax 
    leave 
    ret 
    .size main, .-main 
    .ident "GCC: (Ubuntu 4.4.3-4ubuntu5) 4.4.3" 
    .section .note.GNU-stack,"",@progbits 

Стек до звонка создать:

 +---------------------------+ 
ebp  | saved ebp     | 
     +---------------------------+ 
ebp-4 | age part of struct person | 
     +---------------------------+ 
ebp-8 | no part of struct person | 
     +---------------------------+   
ebp-12 |       | 
     +---------------------------+ 
ebp-16 |       | 
     +---------------------------+ 
ebp-20 | ebp-8 (address)   | 
     +---------------------------+ 

Стек сразу после призвании создать:

 +---------------------------+ 
     | ebp-8 (address)   | 
     +---------------------------+ 
     | return address   | 
     +---------------------------+ 
ebp,esp | saved ebp     | 
     +---------------------------+ 
8

Вот что-то никто не упомянул:

void examine_data(const char *c, size_t l) 
{ 
    c[0] = 'l'; // compiler error 
} 

void examine_data(const struct blob blob) 
{ 
    blob.ptr[0] = 'l'; // perfectly legal, quite likely to blow up at runtime 
} 

Члены const struct являются const, но если этот член является указателем (например, char *), он становится char *const, а не const char * мы действительно хотите. Конечно, мы могли бы предположить, что const является документацией о намерениях, и что любой, кто нарушает это, пишет плохой код (который они есть), но это недостаточно для некоторых (особенно тех, кто просто провел четыре часа, отслеживая причину авария).

Альтернативой может быть создание struct const_blob { const char *c; size_t l } и использование этого, но это довольно беспорядочно - оно попадает в ту же проблему схемы именования, которая у меня есть с указателями typedef. Таким образом, большинство людей придерживаются только двух параметров (или, более вероятно, для этого случая, используя библиотеку строк).

0

Я просто хочу указать одно преимущество передачи ваших структур по значению в том, что оптимизирующий компилятор может лучше оптимизировать ваш код.