Помощь - Поиск - Пользователи - Календарь
Полная версия этой страницы: Динамическое размещение буфера в функции
Форум разработчиков электроники ELECTRONIX.ru > Микроконтроллеры (MCs) > ARM
KnightIgor
Привет всем.

Бывает, что на этапе компиляции размер требуемого буфера внутри функции точно неизвестен. В этом случае используют либо динамическое размещение памяти (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;
}

Ваше мнение, уважаемые коллеги?
scifi
Нельзя самовольно двигать указатель стека, им распоряжается компилятор. Если сдвинуть указатель стека, то потеряются локальные переменные, выделенные в стеке.
У меня есть несколько иной взгляд на эту проблему. Часто было бы удобно динамически выделять память, но с ограничениями: (а) память выделяется в начале программы и не освобождается (инициализация модулей, ну а конец программы - это когда выключили питание) или (б) память выделяется и тут же освобождается (как раз ваш пример). В этом случае нет никакого риска фрагментации динамической памяти, а менеджер памяти может быть простейшим, почти без накладных расходов. Поэтому предлагаю сделать свой упрощенный менеджер памяти: dyn_mem_init(static_buffer, size), malloc(size), free(ptr).
VAI
Здесь посмотрите http://electronix.ru/forum/index.php?showt...mp;#entry374692
Последнее сообщение.
_Артём_
Цитата(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++]);
    }

}


Не подходит?
KnightIgor
Цитата(_Артём_ @ 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, 15:11) *
Компилятор будет ругаться, что data_length должна быть константой. Во всяком случае KEIL.

Что у кейла нет поддержки С99?
Если есть, то должен откомпилировать.
KnightIgor
Цитата(_Артём_ @ 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
_Артём_
Цитата(KnightIgor @ Dec 27 2012, 16:10) *
Поддержка действительно есть (ключ --c99). Пример компилируется. Реализация такого "безразмерного" массива осуществляется через malloc/free:

Точно. Нет бы им SP сдвинуть...
SSerge
А вот __aeabi_memclr4 вызывать было совершенно не обязательно, стандарт не требует обнулять auto переменные (но и не запрещает).
IAR, кстати, тоже через malloc память выделяет.
ReAl
Тю. GCC стек двигает. Причём и для старого C89 варианта
Код
#include <alloca.h>
void SendData(unsigned int data_length)
{
    unsigned char *buffer = alloca(data_length);
    ...
}
реально на входе в функцию на стеке прямым кодом резервируется место согласно data_length (собственно, код не отличается для кода с c99 массивом переменной длины).

Для atmega когда-то использовал с99 массивы переменной длины.
_Pasha
alloca() и еще связным списком попахивает sm.gif Так что лучше с размером-сразу.
ReAl
Цитата(_Pasha @ Dec 27 2012, 19:08) *
alloca() и еще связным списком попахивает sm.gif Так что лучше с размером-сразу.
Я же написал -- в 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}
ReAl
Вдогонку.
Не нашёл ничего древнее 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)) :-)
Для просмотра полной версии этой страницы, пожалуйста, пройдите по ссылке.
Invision Power Board © 2001-2025 Invision Power Services, Inc.