|
|
  |
Динамическое размещение буфера в функции, Если размер изначально неизвестен... |
|
|
|
Dec 27 2012, 10:40
|
Знающий
   
Группа: Участник
Сообщений: 643
Регистрация: 29-05-09
Из: Германия
Пользователь №: 49 725

|
Привет всем. Бывает, что на этапе компиляции размер требуемого буфера внутри функции точно неизвестен. В этом случае используют либо динамическое размещение памяти (malloc), что тянет за собой всю поддержку этого хозяйства, либо "забивается" локальная переменная типа массив с заведомо достаточным размером. Однако его может не хватить при определенных условиях. Пример из собственного опыта: пишем код, который при работе (runtime) должен модифицировать отдельные константы во flash микроконтроллера. В этом случае очевидно нужен буфер на размер страницы flash, чтобы считать ее туда, модифицировать необходимые области и снова скинуть целиком во flash. А теперь представим, что код без перетрансляции (пусть он в библиотеке) должен работать на дериватах микроконтроллера с различным размером страницы flash (например 512 и 2K). У меня возникла идея (наверняка не оригинальная) размещать буфер динамически не в heap, а в локальном стеке. Для этого я написал следующие функции: CODE // --------------------------------------------------------------------------- // // The pair __alloc_sp() and __dealloc_sp() implements a runtime allocation // of memory block within a function. The desired size of the memory buffer // (in bytes) is given by 'size' parameter of __alloc_sp() that returns // then a pointer to the allocated buffer. // // Afterwards, __dealloc_sp() releases the memory. // // The memory is obviously placed in the local stack. // // The idea behind is to provide a possibility to use buffers with the sizes // that may be undefined at compilation time. // static __inline __asm void * __alloc_sp(uint32_t size) { ; align to 32-bit word add r0, r0, #3 bic r0, r0, #3
; prepare the return value sub r0, sp, r0
; save the entry stack pointer for __dealloc_sp() str sp, [r0, #-4]
; move the stack down to the new position to point ; onto its own saved value! mov sp, r0 sub sp, sp, #4 bx lr } static __inline __asm long __dealloc_sp(void) { pop {r0} mov sp, r0 bx lr } Код под KEIL, возможно под GCC какие-то ключевые слова будут выглядеть иначе. Пример использования (абстрактный код): Код unsigned char flash_the_stuff(void *pvar, int size) { int bufsize = GetMCUFlashPageSize(); ... { unsigned char *buffer = __alloc_sp(bufsize); ... memcpy(buffer, pvar, size); ... __dealloc_sp(); } return result; } Ваше мнение, уважаемые коллеги?
Сообщение отредактировал IgorKossak - Dec 27 2012, 17:48
Причина редактирования: [codebox] для длинного кода, [code] - для короткого!!!
|
|
|
|
|
Dec 27 2012, 11:45
|
Гуру
     
Группа: Свой
Сообщений: 3 020
Регистрация: 7-02-07
Пользователь №: 25 136

|
Нельзя самовольно двигать указатель стека, им распоряжается компилятор. Если сдвинуть указатель стека, то потеряются локальные переменные, выделенные в стеке. У меня есть несколько иной взгляд на эту проблему. Часто было бы удобно динамически выделять память, но с ограничениями: (а) память выделяется в начале программы и не освобождается (инициализация модулей, ну а конец программы - это когда выключили питание) или (б) память выделяется и тут же освобождается (как раз ваш пример). В этом случае нет никакого риска фрагментации динамической памяти, а менеджер памяти может быть простейшим, почти без накладных расходов. Поэтому предлагаю сделать свой упрощенный менеджер памяти: dyn_mem_init(static_buffer, size), malloc(size), free(ptr).
|
|
|
|
|
Dec 27 2012, 12:36
|
Гуру
     
Группа: Свой
Сообщений: 2 128
Регистрация: 21-05-06
Пользователь №: 17 322

|
Цитата(KnightIgor @ Dec 27 2012, 12:40)  Привет всем.
Бывает, что на этапе компиляции размер требуемого буфера внутри функции точно неизвестен. В этом случае используют либо динамическое размещение памяти (malloc), что тянет за собой всю поддержку этого хозяйства, либо "забивается" локальная переменная типа массив с заведомо достаточным размером. Был ещё в Си такой вариант: Код void SendData(unsigned int data_length) { unsigned char buffer[data_length]; unsigned char i=0; while (i<data_length) { USART_PutChar(&GSM_UART, buffer[i++]); }
} Не подходит?
|
|
|
|
|
Dec 27 2012, 13:11
|
Знающий
   
Группа: Участник
Сообщений: 643
Регистрация: 29-05-09
Из: Германия
Пользователь №: 49 725

|
Цитата(_Артём_ @ Dec 27 2012, 13:36)  Был ещё в Си такой вариант: Код void SendData(unsigned int data_length) { unsigned char buffer[data_length]; } Компилятор будет ругаться, что data_length должна быть константой. Во всяком случае KEIL. Цитата(VAI @ Dec 27 2012, 12:54)  Посмотрел. Попробовал: как и ожидалось (написал в сообщении #5 здесь), компилятор ругается, желая видеть константы в размерности массива. Я что, один под KEIL пишу? Цитата(scifi @ Dec 27 2012, 12:45)  Нельзя самовольно двигать указатель стека, им распоряжается компилятор. Если сдвинуть указатель стека, то потеряются локальные переменные, выделенные в стеке. Я тоже так предполагал. Но в кодах, что я видел, нет адресаций с базой SP; как правило, на входе указатель на область локальных переменных грузится в какой-либо общий регистр, и уже через него идет обращение. P.S. "Извините, был напуган" (из анекдота): таки да, есть код относительно SP. То есть, мною предложенный код будет работать (и работает) лишь в ограниченных условиях.
Сообщение отредактировал KnightIgor - Dec 27 2012, 13:35
|
|
|
|
|
Dec 27 2012, 14:10
|
Знающий
   
Группа: Участник
Сообщений: 643
Регистрация: 29-05-09
Из: Германия
Пользователь №: 49 725

|
Цитата(_Артём_ @ Dec 27 2012, 14:57)  Что у кейла нет поддержки С99? Поддержка действительно есть (ключ --c99). Пример компилируется. Реализация такого "безразмерного" массива осуществляется через malloc/free: Код AREA ||i.foo||, CODE, READONLY, ALIGN=1
||foo|| PROC ;;;845 ;;;846 int foo(int x) 000000 e92d41f0 PUSH {r4-r8,lr} ;;;847 { 000004 4604 MOV r4,r0 ;;;848 int b[x]; 000006 4626 MOV r6,r4 000008 00b0 LSLS r0,r6,#2 00000a f7fffffe BL malloc 00000e 4605 MOV r5,r0 ;;;849 memset(b, 0, sizeof(b)); 000010 00b1 LSLS r1,r6,#2 000012 4628 MOV r0,r5 000014 f7fffffe BL __aeabi_memclr4 ;;;850 return b[0]; 000018 4628 MOV r0,r5 00001a 682f LDR r7,[r5,#0] 00001c f7fffffe BL free 000020 4638 MOV r0,r7 ;;;851 } 000022 e8bd81f0 POP {r4-r8,pc} ;;;852 // ----------------------------------------------------------------------------- ENDP
|
|
|
|
|
Dec 27 2012, 16:11
|

Нечётный пользователь.
     
Группа: Свой
Сообщений: 2 033
Регистрация: 26-05-05
Из: Бровари, Україна
Пользователь №: 5 417

|
Тю. GCC стек двигает. Причём и для старого C89 варианта Код #include <alloca.h> void SendData(unsigned int data_length) { unsigned char *buffer = alloca(data_length); ... } реально на входе в функцию на стеке прямым кодом резервируется место согласно data_length (собственно, код не отличается для кода с c99 массивом переменной длины). Для atmega когда-то использовал с99 массивы переменной длины.
--------------------
Ну, я пошёл… Если что – звоните…
|
|
|
|
|
Dec 27 2012, 19:36
|

Нечётный пользователь.
     
Группа: Свой
Сообщений: 2 033
Регистрация: 26-05-05
Из: Бровари, Україна
Пользователь №: 5 417

|
Цитата(_Pasha @ Dec 27 2012, 19:08)  alloca() и еще связным списком попахивает  Так что лучше с размером-сразу. Я же написал -- в GCC для примера выше код с alloca и с C99-массивом переменного размера одинаков. alloca был придуман именно для того, чтобы для "динамической" памяти, которая освобождается в той же функции, в которой выделялась, уйти от списков и фрагментации. Ценой необходимости запаса не стеке. В некотором смысле C99-массивы переменной длины — это синтаксический сахар над alloca. Ну или alloca -- предшественник этих массивов. Цитата NAME alloca - allocate memory that is automatically freed
SYNOPSIS #include <alloca.h>
void *alloca(size_t size);
DESCRIPTION The alloca() function allocates size bytes of space in the stack frame of the caller. This temporary space is automatically freed when the function that called alloca() returns to its caller. Никакие списки тут не нужны. Код #include <alloca.h>
void moo(int *p);
void foo(int i, int j) { if (i > 0) { int *p = alloca(i); moo(p); } if (j > 0) { int *p = alloca(2*j); moo(p); } } Код foo: @ args = 0, pretend = 0, frame = 0 @ frame_needed = 1, uses_anonymous_args = 0 push {r3, r4, r7, lr} cmp r0, #0 add r7, sp, #0 mov r4, r1 ble .L2 adds r0, r0, #14 bic r0, r0, #7 sub sp, sp, r0; выделили первый запрос mov r0, sp bl moo .L2: cmp r4, #0 ble .L1 lsls r4, r4, #1 adds r4, r4, #14 bic r4, r4, #7 sub sp, sp, r4; выделили второй запрос mov r0, sp bl moo .L1: mov sp, r7; освобождаем одним махом всё (если вообще выделяли) pop {r3, r4, r7, pc}
--------------------
Ну, я пошёл… Если что – звоните…
|
|
|
|
|
Dec 28 2012, 06:57
|

Нечётный пользователь.
     
Группа: Свой
Сообщений: 2 033
Регистрация: 26-05-05
Из: Бровари, Україна
Пользователь №: 5 417

|
Вдогонку. Не нашёл ничего древнее BC 5.02 разлива 97-го года (в каталоге /media/U2/dosbox_c для запуска в dosbox для такого рода проверок). Тот же тест, только alloca сидит в malloc.h, а не в персональном файле. CODE _foo proc near ; ; void foo(int i, int j) ; push bp mov bp,sp sub sp,2 push si push di mov ax,word ptr [bp+4] ; ; { ; if (i > 0) { ; or ax,ax jle short @1@6 ; ; int *p = alloca(i); ; mov dx,ax inc dx and dx,-2 neg dx add dx,sp sub dx,10 cmp dx,sp jbe short @1@4 xor ax,ax jmp short @1@5 @1@4: mov sp,dx mov ax,dx @1@5: mov word ptr [bp-2],ax ; ; moo(p); ; push ax call near ptr _moo add sp,2 @1@6: ; ; } ; if (j > 0) { ; cmp word ptr [bp+6],0 jle short @1@11 ; ; int *p = alloca(2*j); ; mov dx,word ptr [bp+6] add dx,dx inc dx and dx,-2 neg dx add dx,sp sub dx,10 cmp dx,sp jbe short @1@9 xor ax,ax jmp short @1@10 @1@9: mov sp,dx mov ax,dx @1@10: mov word ptr [bp-2],ax ; ; moo(p); ; push ax call near ptr _moo add sp,2 @1@11: ; ; } ; } ; lea sp,word ptr [bp-6] pop di pop si leave ret _foo endp Всё чисто, просто выделение на стеке на правах временных переменных с такой же очисткой при помощи leave как и для обычных временных переменных. Кстати, надо было бы alloca(i*sizeof(int)) :-)
--------------------
Ну, я пошёл… Если что – звоните…
|
|
|
|
|
  |
1 чел. читают эту тему (гостей: 1, скрытых пользователей: 0)
Пользователей: 0
|
|
|