1

Тема: Масив, як параметр функції.

Вітаю всіх. Поясніть мені, що відбувається з виділенням пам'яті при виконанні функції foo в наступному коді:

void foo(char arr[5])
{
  for (int i = 0; i < sizeof(arr); i++) {
    printf("%c", arr[i]);
  }
  printf("\n");
}
 
int main()
{
  char* str = "hello";
  foo(str);
}

Очевидно, вказівнику arr буде присвоєно значення str. Але незрозуміло, що відбувається в частині char arr[5].
Поки я розумію це так, що при виконанні функції foo на стеку буде виділено 5 байт пам'яті, але вказівнику arr не буде надано адресу початку цієї пам'яті, а буде присвоєно адресу покажчика str. 5 байт будуть даремно виділені?

Подякували: koala1

2

Re: Масив, як параметр функції.

В функції фу буде виділено 5 байт під масив, ще 4 байти під змінну і, і ще скількісь у стеку, для адрес повернення.
Стосовно сучасних компілерів, якщо змінна не використовується, то оптимізатор при компіляції може і не надавати їй памяті, то вже від налаштувань компілера залежити буде.

Подякували: mimik1

3 Востаннє редагувалося koala (27.10.2020 16:53:24)

Re: Масив, як параметр функції.

Пан Chemist-i помиляється.
Масиви в C і C++ передаються виключно вказівниками. char arr[5] як параметр - лише натяк для програміста, що параметром має бути масив з 5 елементів; насправді arr - це char *, і ви можете навіть його змінювати (тільки в проді так не робіть!). sizeof(arr) буде дорівнювати sizeof(char*). Виділення пам'яті під масив виконується один раз - статичний, на етапі компіляції, під 6 байтів "hello" (з останнім '\0').
Ну і уточнюйте, яка мова. C і C++ трохи по-різному працюють з вказівниками - тут, наприклад, очевидно, що це C, але нескладно побудувати приклад, де це не буде помітно.

Ось демонстрація: https://ideone.com/cV8Yrg

Подякували: FakiNyan, mimik, Chemist-i, HetmanNet, leofun015

4

Re: Масив, як параметр функції.

Перевірив, дійсно... Знав, що sizeof вказівника дасть розмір вказівника. Але так само знаю, що sizeof масиву дасть розмірність масиву. А чому тоді масиви передаються за посиланнями? Посилання це вроді &, а тут буде вроді як *

Мова С++, а що у мові С оголошення масиву в параметрі функції має значення?

5 Востаннє редагувалося koala (27.10.2020 16:58:07)

Re: Масив, як параметр функції.

mimik написав:

Посилання це вроді &, а тут буде вроді як *

Перепрошую, mea culpa. Звісно, вказівником передається, зараз виправлю.

mimik написав:

Але так само знаю, що sizeof масиву дасть розмірність масиву

sizeof - це, здається, єдина операція, яка працює з масивом як із масивом. Усі інші використовують масив як вказівник на початковий елемент:

arr==&arr[0]

Зокрема, саме цей вказівник передається в функцію. І функція не матиме зеленого уявлення про те, на що саме цей вказівник указує, відтак і sizeof не спрацює. І так, навіть операція індексування працює зі вказівником:

a[i] == *(a+i)

6 Востаннє редагувалося wander (27.10.2020 17:13:32)

Re: Масив, як параметр функції.

mimik написав:

Мова С++

char* str = "hello";

Ваш код неможливо буде скомпілювати. В С++ таке не допускається.

mimik написав:

Очевидно, вказівнику arr буде присвоєно значення str.

Вірно. Але не "присвоєно", а при виконанні функції вказівник arr (параметр) буде ініціалізований значенням вказівника str (аргумент).

mimik написав:

Але незрозуміло, що відбувається в частині char arr[5].

Ніякої розмірності немає, тому що ніякого масиву теж немає. Ви ж самі сказали: arr - це вказівник. Це саме вказівник, а не масив. У списку параметрів функції оголошення масиву неявно трансформується в оголошення вказівника. Тобто char[5] трансформується в char*. Всі ці [] і 5 просто ігноруються.

mimik написав:

Поки я розумію це так, що при виконанні функції foo на стеку буде виділено 5 байт пам'яті, але вказівнику arr не буде надано адресу початку цієї пам'яті, а буде присвоєно адресу покажчика str. 5 байт будуть даремно виділені?

Ні, нічого виділено не буде взагалі.

У вас у всьому коді взагалі немає масивів, як таких, крім одного-єдиного неіменованого масиву: літералу "hello", який має тип const char[6].

mimik написав:

а що у мові С оголошення масиву в параметрі функції має значення?

Ні.

Хіба що в сучасному С є маловідома фіча: declaration of a parameter

void foo(char arr[static 5])
Подякували: mimik, leofun012

7

Re: Масив, як параметр функції.

wander написав:

В С++ таке не допускається.

У стандарті - так. Але багацько компіляторів обмежуються попередженням, щоб можна було legacy C компілювати.

8

Re: Масив, як параметр функції.

koala написав:

У стандарті - так.

Відповідно й у мові програмування С++ також.

koala написав:

Але багацько компіляторів обмежуються попередженням, щоб можна було legacy C компілювати.

Якщо правильно налаштувати компілятор, то видасть помилку (і не лише на це), а не попередження.
Проте це вже інша історія.

Прихований текст
Compiler returned: 1
Compiler stderr
<source>: In function 'void foo(char*)':
<source>:6:30: error: 'sizeof' on array function parameter 'arr' will return size of 'char*' [-Werror=sizeof-array-argument]
    6 |   for (int i = 0; i < sizeof(arr); i++) {
      |                             ~^~~~
<source>:4:15: note: declared here
    4 | void foo(char arr[5])
      |          ~~~~~^~~~~~
<source>:6:21: error: comparison of integer expressions of different signedness: 'int' and 'long unsigned int' [-Werror=sign-compare]
    6 |   for (int i = 0; i < sizeof(arr); i++) {
      |                   ~~^~~~~~~~~~~~~
<source>: In function 'int main()':
<source>:14:15: error: ISO C++ forbids converting a string constant to 'char*' [-Werror=write-strings]
   14 |   char* str = "hello";
      |               ^~~~~~~
Подякували: mimik, leofun012

9

Re: Масив, як параметр функції.

А в чому сенс там використання static в С?

10 Востаннє редагувалося koala (27.10.2020 18:37:32)

Re: Масив, як параметр функції.

От саме у цьому і є.

C99 standard 6.7.5.3 a.7 написав:

A declaration of a parameter as ‘‘array of type’’ shall be adjusted to ‘‘qualified pointer to
type’’, where the type qualifiers (if any) are those specified within the [ and ] of the
array type derivation. If the keyword static also appears within the [ and ] of the
array type derivation, then for each call to the function, the value of the corresponding
actual argument shall provide access to the first element of an array with at least as many
elements as specified by the size expression.

Тобто у ключового слова static є додаткове значення - якщо воно вжито так, як показав пан wander, то параметром має бути масив із щонайменше стількома елементами, а не вказівник. Хоча передаватися все одно буде вказівник, просто компілятор контролюватиме, щоб підставляли саме масив.

11

Re: Масив, як параметр функції.

Я б сказав, що в такому випадку дане оголошення говорить компілятору, що він може сміливо вважати, що arr вказуватиме на початок масиву з як мінімум 5 елементів. Розмір в цьому випадку виступає лише як підказка компілятору та оптимізатору. Вони можуть вважати, що доступ до перших 5 елементів вказаного масиву завжди безпечний і, відповідно, генерувати більш ефективний код, спираючись на цю гарантію. Зокрема, компілятор має право вважати, що переданий вказівник не є нульовим.

Прихований текст

В С, до речі, є ще одна незвичайна можливість, в якій розмір масиву в оголошенні параметра функції "не зовсім ігнорується". Це використання виразів з побічними ефектами при оголошенні VLA в списку параметрів

#include <stdio.h>
 
int n = 0;
 
void foo(int a[++n])
{
  printf("%d\n", n);
}
 
int main()
{
  foo(0);
  foo(0);
  foo(0);
}

На тип параметра це не впливає, тобто параметр є звичайним вказівником, але тим не менше при кожному виклику foo значення n буде збільшуватися. Це вже, звичайно, цілковитий сюр, але однак така особливість в С є.
https://godbolt.org/z/js6GaP

Подякували: leofun011

12 Востаннє редагувалося ReAl (28.10.2020 16:32:21)

Re: Масив, як параметр функції.

wander написав:

В С, до речі, є ще одна незвичайна можливість, в якій розмір масиву в оголошенні параметра функції "не зовсім ігнорується". Це використання виразів з побічними ефектами при оголошенні VLA в списку параметрів
...
Це вже, звичайно, цілковитий сюр, але однак така особливість в С є.

Починаючи з C99, але на жаль не всі компілятори і в 2005 підтримували повний C99, це ніякий не сюр, а страшенно зручна штука для

void foo(int sx, int sy, int arr[sy][sx])
{
    for (int y = 0; y < sy; ++y)
        for (int x = 0; x < sx; ++x)
            moo(arr[y][x]);
}

Відповідно, якщо вже arr[y][x] оброблюється правильно, то на тип таки впливає :-)

13

Re: Масив, як параметр функції.

ReAl написав:

але на жаль не всі компілятори і в 2005 підтримували повний C99

Скажу більше, навіть не всі сучасні компілятори чесно підтримують весь функціонал С99.

ReAl написав:

Відповідно, якщо вже arr[y][x] оброблюється правильно, то на тип таки впливає :-)

Так, [sx][sy], зрозуміло, впливає на тип arr (точніше, тільки [sy] впливає).
arr тут отримує варіабельний тип (variably modified type).

14 Востаннє редагувалося wander (28.10.2020 18:30:04)

Re: Масив, як параметр функції.

ReAl написав:

це ніякий не сюр, а страшенно зручна штука

Щодо сюру, в даному контексті я не мав на увазі, що це щось погане чи абсурдне. Це швидше абсурдно цікава конструкція для мене, як С++ розробника. В загальному зрозуміло, що це дуже зручна і недооцінена властивість C99 i VLA. З якоїсь незрозумілої причини, коли заходить мова про VLA, то на передній план найчастіше вилазять міркування про можливість створення масивів заздалегідь невідомого розміру в локальній пам'яті, тобто в стеці. Це викликає подив, бо насправді можливість локального оголошення таких масивів - це абсолютно побічна і другорядна властивість VLA, ніякої суттєвої ролі в функціональності VLA не грає. Як правило, випинання цієї можливості і прихованим за нею підводним камінням робиться сліпими критиками VLA з метою відвести обговорення від суті питання.

А суть питання полягає в тому, що підтримка VLA - це в першу і головну чергу потужне розширення системи типізації мови. Це введення в мову С такої фундаментально нової концептуальної групи типів, як variably modified types. Всі найбільш важливі внутрішні та реалізаційні деталі VLA прив'язані саме до цього типу, а не до самого об'єкту як такого. Саме введення в мову варіабельних типів є "справжнім" айсбергом VLA, а можливість створювати об'єкти таких типів в локальній пам'яті - це не більше ніж незначна (і необов'язкова) верхівка цього айсберга.

Подякували: koala, ReAl, mimik, 0xDADA11C74

15

Re: Масив, як параметр функції.

Почитав детальніше про цей VLA, але хіба в С++ він не підтримується?
Як я розумію деякі компілятори використовують VLA в С++?