Будет ли экономиться размер файла если в параметры функции передавать не значения а указатели?

Например есть 2-е функции

void func1(int ValueA, int ValueB)
{
    int a = ValueA + ValueA;
    int b = ValueB + ValueB;
}

void func2(int* PtrValueA, int* PtrValueB)
{
    int a = *PtrValueA + *PtrValueA;
    int b = *PtrValueB + *PtrValueB;

}

int main()
{
    int a = 10, b = 20;
    func1(a, b);
    func2(&a, &b);

}

Если я правильно понял то если в функцию передавать значения а не указатели как в func1 то при компиляции для параметров функции func1 будет выделена доп. память т.е. будет выделена память на переменные a, b в функции main + будет выделена память для параметров func1. Из этого у меня и возник вопрос с точки зрения размера файла не будет ли выгоднее передавать указатели?


Ответы (2 шт):

Автор решения: AlexGlebe

Память для переменных может выделятся в стеке (сразу выделенный участок памяти при старте программы) а не в файле. Размер файла не будет меняться, так как место памяти, где хранится программа и локальные переменные функции - разные.

По-умолчанию в Си переменные имеют отметку автоматического хранения :

auto int a = ..

это значит, что переменные могут храниться или в стеке или в регистрах процессора или в самих командах mov %%rax, 10.

При передачи указателями void func2(int* PtrValueA, int* PtrValueB) компилятору уменьшается возможность оптимизировать и код функции должен брать значения по выданному указателю и так-же для передачи аргументов, должна быть выделена память (часть стека).

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

Ответ :

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

Обычно простые переменные передают копией (с надеждой на оптимизацию), а структуры/объекты указателем/ссылкой.

→ Ссылка
Автор решения: LShadow77

Тоже отвечу, но немного с другой колокольни. Если речь идёт об экономии размера скомпилированного "экзешника", то вопрос смысла не имеет. В исполняемых файлах присутствуют выравнивания, так что одним байтом больше, одним меньше - разницы не будет никакой. А вот что действительно имеет значение, так это производительность кода. И да, передача параметров типа int по значению будет эффективнее, чем передача указателей, т.к. в последнем случае понадобятся дополнительные операции по извлечению этих самых значений из указателей (разыменование). Так что если функция оперирует значениями передаваемых простых переменных (имеющих тип int, float, и т.д.), то и передавать следует значения этих переменных, а не указатели.

Однако для ясности разберём ваш пример. Дизассемблируем ваш код и посмотрим, сколько машинных инструкций и какие содержат функции func1() и func2(). Пугаться ассемблера тут не стоит, ибо нас интересует в данный момент только количество сгенерированных компилятором инструкций, а их смысл я дополнительно укажу в комментариях. И так, вот он, вид функции func1() после компиляции:

func1     proc near
          retn
func1     endp

Теперь функция func2():

func2     proc near
          retn
func2     endp

Как видим, обе функции содержат только одну инструкцию - retn (нетрудно догадаться, что это оператор return;). Обе функции ничего не делают и занимают в коде ровно 1 байт! Как так получилось? А просто ваши функции действительно ничего не делают. Да, там присутствуют какие-то арифметические вычисления над локальными переменными, однако результат никуда не сохраняется, локальные переменные a и b после возврата уничтожаются. Компилятор увидел это, и весь ненужный код удалил.

Так что сделаем так, чтобы обе функции сохраняли результат своих вычислений, ну скажем, в некоторую дополнительную структуру, указатель на которую (именно указатель, а не значение), передадим через третий параметр. Выглядеть это будет так:

struct RESULT
{
    int a;
    int b;
};

void func1(int ValueA, int ValueB, RESULT* pRes)
{
    pRes->a = ValueA + ValueA;
    pRes->b = ValueB + ValueB;
}

void func2(int* PtrValueA, int* PtrValueB, RESULT* pRes)
{
    pRes->a = *PtrValueA + *PtrValueA;
    pRes->b = *PtrValueB + *PtrValueB;
}

int main()
{
    int a = 10, b = 20;
    RESULT res;
    func1(a, b, &res);
    func2(&a, &b, &res);
    return 0;
}

Теперь первая функция в скомпилированном коде будет выглядеть так (смысл каждой инструкции прокомментирован):

func1           proc near

ValueA          = dword ptr  4
ValueB          = dword ptr  8
pRes            = dword ptr  0Ch

                mov     eax, [esp+ValueA] ; берём ValueA
                mov     edx, [esp+pRes] ; берём указатель pRes
                add     eax, eax        ; складываем ValueA + ValueA
                mov     [edx], eax      ; записываем результат в pRes->a
                mov     eax, [esp+ValueB] ; берём ValueB
                add     eax, eax        ; складываем ValueB + ValueB
                mov     [edx+4], eax    ; записываем результат в pRes->b
                retn                    ; выходим из функции
func1           endp

А вторая так:

func2           proc near

PtrValueA       = dword ptr  4
PtrValueB       = dword ptr  8
pRes            = dword ptr  0Ch

                mov     eax, [esp+PtrValueA] ; берём указатель PtrValueA
                mov     edx, [esp+pRes] ; берём указатель pRes
                mov     eax, [eax]      ; извлекаем значение *PtrValueA
                add     eax, eax        ; складываем *PtrValueA + *PtrValueA
                mov     [edx], eax      ; записываем результат в pRes->a
                mov     eax, [esp+PtrValueB] ; берём указатель PtrValueB
                mov     eax, [eax]      ; извлекаем значение *PtrValueB
                add     eax, eax        ; складываем *PtrValueB + *PtrValueB
                mov     [edx+4], eax    ; записываем результат в pRes->b
                retn                    ; выходим из функции
func2           endp

И того, функция func1() содержит 8 команд процессора, а функция func2() - уже 10 комманд, т.к. две дополнительные инструкции приходятся на разыменование указателей. Так что первая функция хоть чуть-чуть, но производительнее второй. А что касается их размера в байтах, то он неважен. ОК, вторая функция на 4 байта длинней первой - это фитюлька, которую вообще не стоит учитывать! Тем более, что из-за выравнивания на размер кода в целом эта разница никак не скажется.

Ещё раз: производительность - вот, на что надо ориентироваться при написании кода, а не на размер (а если всё таки хочется уменьшить размер исполняемого файла, то для этого существуют специальные архиваторы-упаковщики, на подобие UPX и ему подобные).

И да, выше приведённый разбор справедлив, если в качестве параметров передаются простые типы данных, такие как int, float, double - всё, что до восьми байт длинной. Большие же массивы и структуры целесообразно передавать через указатель или ссылку, в противном случае процесс передачи таких больших значений будет занимать несравненно больше времени, чем обращения по указателю.

Надеюсь, я вам подкинул пищу для размышлений))

→ Ссылка