1

Тема: Адресація параметрів функції

Доброго дня, панове. Одразу хочу повідомити, що працюю на 64-розрядній системі Windows, надалі це, напевно, знадобиться. Розбирався сьогодні з черговою лабораторною, темою якої є функції з неоголошеними параметрами. У ній мені довелося розробити три версії функції, яка повертала б номер точки найвіддаленішої від початку координат. Точка задається структурою, полями якої є 3 змінні типу double. У першій версії функції спочатку вказується кількість параметрів, а далі самі параметри-структури; у другій версії мені потрібно було перед кожною точкою вказувати її номер. Для зручності я використав бібліотеку stdarg.h. З цими версіями труднощі не виникли, цікавіше стало з третьою версією. Параметри такі ж, але я вирішив використати нетипізований вказівник, щоб опрацьовувати параметри. Перше, що я помітив це те, що моя структура розширяється компілятором для вирівнювання з 24 до 64 байтів. Наступним я помітив, що якщо, наприклад, порядок параметрів: int, Point(користувацький тип структури), int, то адреса параметра-структури буде більшою, ніж другого цілочислового значення. Це мені видалося трохи дивним і одночасно цікавим, адже, наскільки я знаю, параметри розташовуються в стеці по-порядку. Отож я взяв цей тип структури, розробив кілька тестових функцій з різними параметрами й виводив адреси параметрів. Результати були такі ж. Хотів взнати з чим зв'язано таке розташування об'єктів в пам'яті. Нижче прикріплю код тестової програми і скріншоти з результатами.

#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <stdarg.h>
#define SQR(x) ((x)*(x))

typedef struct {
    double x, y, z;
} Point;

void TestParams1(int, double, Point, int, double, Point);
void TestParams2(Point, Point, double, int, int, double);
void TestParams3(int, int, Point, Point, double, double);
void TestParams4(double, int, Point, int, int*, Point*, Point, double, double*);

int main(void) {
    system("chcp 1251");

    Point a = { 2.5, 6.3, 10.0 }, b = { 7.2, 14.1, 3.05 }, c = { 2.0, 3.8, 4.7 }, d = { 10.6, 3.4, 5.0 }, e = { 3.2, 10.9, 7.6 };
    int int_num = 432;
    double dob_num = 23.12;

    /*Тест 1*/
    printf("Тест 1:\n");
    TestParams1(1, 4.0, a, 10, 6.54, b);

    /*Тест 2*/
    printf("Тест 2:\n");
    TestParams2(c, d, 10.23, 4, 3, 87.32);

    /*Тест 3*/
    printf("Тест 3:\n");
    TestParams3(1, 4, a, e, 6.1, 3.21);

    /*Тест 4*/
    printf("Тест 4:\n");
    TestParams4(23.6, 4, c, 11, &int_num, &d, a, 65.87, &dob_num);
}

void TestParams1(int arg_int1, double arg_dob1, Point arg_Point1, int arg_int2, double arg_dob2, Point arg_Point2)
{
    printf(" &arg_int1=%p, &arg_dob1=%p, &arg_Point1=%p \n", &arg_int1, &arg_dob1, &arg_Point1);
    printf(" &arg_int2=%p, &arg_dob2=%p, &arg_Point2=%p \n", &arg_int2, &arg_dob2, &arg_Point2);
}
void TestParams2(Point arg_Point1, Point arg_Point2, double arg_dob1, int arg_int1, int arg_int2, double arg_dob2)
{
    printf(" &arg_Point1=%p, &arg_Point2=%p, &arg_dob1=%p \n", &arg_Point1, &arg_Point2, &arg_dob1);
    printf(" &arg_int1=%p, &arg_int2=%p, &arg_dob2=%p,  \n", &arg_int1, &arg_int2, &arg_dob2);
}
void TestParams3(int arg_int1, int arg_int2, Point arg_Point1, Point arg_Point2, double arg_dob1, double arg_dob2)
{
    printf(" &arg_int1=%p, &arg_int2=%p, &arg_Point1=%p  \n", &arg_int1, &arg_int2, &arg_Point1);
    printf(" &arg_Point2=%p, &arg_dob1=%p, &arg_dob2=%p, \n", &arg_Point2, &arg_dob1, &arg_dob2);
}
void TestParams4(double arg_dob1, int arg_int1, Point arg_Point1, int arg_int2, int* p_int1, Point* p_Point1, Point arg_Point2, double arg_dob2, double* p_dob1)
{
    printf(" &arg_dob1=%p, &arg_int1=%p, &arg_Point1=%p  \n", &arg_dob1, &arg_int1, &arg_Point1);
    printf(" &arg_int2=%p, &p_int1=%p, &p_Point1=%p  \n", &arg_int2, &p_int1, &p_Point1);
    printf(" &arg_Point2=%p, &arg_dob2=%p, &p_dob1=%p, \n", &arg_Point2, &arg_dob2, &p_dob1);
}

Тестові функції:
https://drive.google.com/file/d/1PBC3y-U6tjO8Nqisb6QLA9GVcaRnrb3L/view?usp=sharing

Результати:
https://drive.google.com/file/d/16IGMi5HLZIvmQhGth6wgkd6RUiXLtehO/view?usp=sharing

2

Re: Адресація параметрів функції

Чомусь зображення не додалися, тому залишу посилання на них:
Тестові функції:
https://drive.google.com/file/d/1PBC3y- … sp=sharing

Результати:
https://drive.google.com/file/d/16IGMi5 … sp=sharing

3

Re: Адресація параметрів функції

Залийте картинки нормально на якийсь хостинг або сюди на сайт (така функція теж присутня). Все одно на ваш диск не пускає.

Foxy4 написав:

вирішив використати нетипізований вказівник

Не типізований - це ви про void*? Не бачу, щоб ви його використовували.

Foxy4 написав:

я помітив, що моя структура розширяється компілятором для вирівнювання з 24 до 64 байтів

*SCRATCH*
А, як ви це помітили, якщо не секрет?

4

Re: Адресація параметрів функції

wander написав:

Залийте картинки нормально на якийсь хостинг або сюди на сайт (така функція теж присутня). Все одно на ваш диск не пускає.

Ніколи не додавав сюди картинки, тому не з першого разу вийшло.

wander написав:

Не типізований - це ви про void*? Не бачу, щоб ви його використовували.

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

wander написав:

*SCRATCH*
А, як ви це помітили, якщо не секрет?

int FarthestPoint1(unsigned count, Point a, ...) {
    double max_distance = 0, curr_distance;
    int point_num = 1;
    Point* ppoint;
    for ( ppoint = &a; count > 0; count--, (char*)ppoint += 64) {
        curr_distance = CalcDistance(ppoint);
        if (curr_distance > max_distance) {
            max_distance = curr_distance;
            point_num = ((int)ppoint - (int)&a)/64 + 1;
        }
    }
    return point_num;
}

Під час дебагу цього коду. Це перша версія функції. Як можна помітити я використовую вказівник на структури Point* ppoint, адже усіма необов'язковими параметрами повинні бути саме вони. Спочатку з кожною ітерацію я переміщав вказівник на наступний елемент, просто додаючи 1, але він не потрапляв на наступний параметр. Тоді я згадав про вирівнювання і вирішив перемістити вказівник одразу на 3 позиції, тобто якщо моя структура займає 24 байти, то вказівник я переміщаю на 72. Чудо, я потрапляю на останні 16 байт наступної структури. Тобто я "перескакую" лишні 8 байт, якраз стільки займає моє перше поле структури. Отже, 72-8=64 байти.

Ще раз прикріплю функції, які були використані для тесту:

void TestParams1(int arg_int1, double arg_dob1, Point arg_Point1, int arg_int2, double arg_dob2, Point arg_Point2)
{
    printf(" &arg_int1=%p, &arg_dob1=%p, &arg_Point1=%p \n", &arg_int1, &arg_dob1, &arg_Point1);
    printf(" &arg_int2=%p, &arg_dob2=%p, &arg_Point2=%p \n", &arg_int2, &arg_dob2, &arg_Point2);
}
void TestParams2(Point arg_Point1, Point arg_Point2, double arg_dob1, int arg_int1, int arg_int2, double arg_dob2)
{
    printf(" &arg_Point1=%p, &arg_Point2=%p, &arg_dob1=%p \n", &arg_Point1, &arg_Point2, &arg_dob1);
    printf(" &arg_int1=%p, &arg_int2=%p, &arg_dob2=%p,  \n", &arg_int1, &arg_int2, &arg_dob2);
}
void TestParams3(int arg_int1, int arg_int2, Point arg_Point1, Point arg_Point2, double arg_dob1, double arg_dob2)
{
    printf(" &arg_int1=%p, &arg_int2=%p, &arg_Point1=%p  \n", &arg_int1, &arg_int2, &arg_Point1);
    printf(" &arg_Point2=%p, &arg_dob1=%p, &arg_dob2=%p, \n", &arg_Point2, &arg_dob1, &arg_dob2);
}
void TestParams4(double arg_dob1, int arg_int1, Point arg_Point1, int arg_int2, int* p_int1, Point* p_Point1, Point arg_Point2, double arg_dob2, double* p_dob1)
{
    printf(" &arg_dob1=%p, &arg_int1=%p, &arg_Point1=%p  \n", &arg_dob1, &arg_int1, &arg_Point1);
    printf(" &arg_int2=%p, &p_int1=%p, &p_Point1=%p  \n", &arg_int2, &p_int1, &p_Point1);
    printf(" &arg_Point2=%p, &arg_dob2=%p, &p_dob1=%p, \n", &arg_Point2, &arg_dob2, &p_dob1);
}

Результат, де виводяться адреси параметрів по-порядку їх запису у функцію:
http://replace.org.ua/extensions/om_images/img/6412c5a75fb06/2023-03-15-225210.png

5 Востаннє редагувалося wander (16.03.2023 12:50:03)

Re: Адресація параметрів функції

Foxy4 написав:

необов'язковими параметрами

Що ви називаєте необов'язковими параметрами? Еліпсис чи що?
Я взагалі гублюся у вашій термінології, звідки ви її берете з підручника? Чи викладач так пояснює?

Foxy4 написав:

Чудо, я потрапляю на останні 16 байт наступної структури

А, звідки ви взяли наступну структуру?

int FarthestPoint1(unsigned count, Point a /*<- це не масив*/, ...)
(char*)ppoint += 64 // ви тут робите зміщення на "сиру" пам'ять, дивно, що цей код у вас взагалі не падає
(int)ppoint // це UB
(int)&a // і це теж UB

UPD - Я зрозумів, що ви хочете зробити..
Вам сюди Функції зі змінною кількістю параметрів. Особливо зверніть увагу на повідомлення пана 0xDADA11C7 про угоди для викликів.

Подякували: Foxy4, leofun012

6

Re: Адресація параметрів функції

Я гадаю, ви намагаєтеся розібратися з угодами про виклик. Для початку могли б хоча б компілятор назвати; але швидше за все під Windows буде Microsoft x64, перші кілька параметрів передаються через регістри, а те, що ви виводите - це адреси тимчасових змінних. Ну і wander правий, з точки зору C це все - UB. Можете загнати свій код на https://godbolt.org/ (чи зневаджувати асемблер на своєму комп'ютері) і подивитися, що там насправді відбувається.

Подякували: Foxy4, leofun012

7

Re: Адресація параметрів функції

wander написав:

Що ви називаєте необов'язковими параметрами? Еліпсис чи що?
Я взагалі гублюся у вашій термінології, звідки ви її берете з підручника? Чи викладач так пояснює?

Так, під необов'язковими параметрами я мав на увазі еліпсис. Перепрошую, не знав про цей термін.

wander написав:

А, звідки ви взяли наступну структуру?

Ось звідси:

Point a = { 2.5, 6.3, 10.0 }, b = { 7.2, 14.1, 3.05 }, c = { 2.0, 3.8, 4.7 }, d = { 10.6, 3.4, 5.0 }, e = { 3.2, 10.9, 7.6 };
printf("Функція 1: найвіддаленніша точка - %d\n", FarthestPoint1(5, a, b, c, d, e));

8

Re: Адресація параметрів функції

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

У мене є питання не до вас, а до того хто дає такі лабораторні та навчає писати такого роду код. Такі теми, як ця набагато краще розбирати з точки зору асемблера, можна у зв'язці з С, брати асемблерний вихлоп і його розбирати. Інакше ви толком нічого не зрозумієте, що ж там "під капотом" відбувається і як воно виглядає. А, спроби робити такого роду "хаки" ні до чого хорошого не приведуть.

9

Re: Адресація параметрів функції

wander написав:

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

Уважно ознайомився з усім матеріалом, щось правда не зрозумів, але постараюся все-таки розібратися як записуються параметри в даному випадку.

wander написав:

У мене є питання не до вас, а до того хто дає такі лабораторні та навчає писати такого роду код. Такі теми, як ця набагато краще розбирати з точки зору асемблера, можна у зв'язці з С, брати асемблерний вихлоп і його розбирати. Інакше ви толком нічого не зрозумієте, що ж там "під капотом" відбувається і як воно виглядає. А, спроби робити такого роду "хаки" ні до чого хорошого не приведуть.

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

10

Re: Адресація параметрів функції

Foxy4 написав:

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

А, можете викласти сюди повний опис лабораторної?
Бо цей код, що ви продемонстрували з еліпсисом не є стандартним, вірніше зовсім навпаки. Тобто, якщо вам цього не давали на лекціях/лабораторних - це означає, що ви самі десь знайшли цей код з ітерацією по параметрах еліпсису. Бо схожий підхід я раніше зустрічав, особливо всякі русняві помийки таке публікують. Більшість адекватних ресурсів намагаються такого роду код викорінювати, тому..
Щодо вирівнювання і ваших труднощів, то тут немає про що говорити. Ваш код це суцільна невизначена поведінка (UB), про ніякі вирівнювання тут вже мова не йде, навіть якби вони були. Мова С надає коректний інструмент для ваших цілей - va_arg.

11

Re: Адресація параметрів функції

wander написав:

Ну суть лабораторної було ознайомитися з функціями з неоголошеними параметрами. Просто мені попалося завдання зі структурами в якості параметрів. Ніхто не передбачив, що компілятор буде їх вирівнювати і внаслідок цього виникнуть певні труднощі.
А, можете викласти сюди повний опис лабораторної?

Лекція і відповідно лабораторна до неї. Якщо буде цікаво, то мій варіант №20. Прикріплю також свій код, але, на жаль, я ще не додав коментарі.

#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <stdarg.h>
#define SQR(x) ((x)*(x))

typedef struct {
    double x, y, z;
} Point;

double CalcDistance(Point*);
int FarthestPoint1(unsigned, Point, ...);
int FarthestPoint2(int, Point, ...);

int main(void) {
    system("chcp 1251");

    Point a = { 2.5, 6.3, 10.0 }, b = { 7.2, 14.1, 3.05 }, c = { 2.0, 3.8, 4.7 }, d = { 10.6, 3.4, 5.0 }, e = { 3.2, 10.9, 7.6 };

    printf("Функція 1: найвіддаленніша точка - %d\n", FarthestPoint1(5, a, b, c, d, e));
    printf("Функція 2: найвіддаленніша точка - %d\n", FarthestPoint2(1, a, 2, b, 3, c, 4, d, 5, e, '#'));
}

double CalcDistance(Point* p) {
    return (double)sqrt(SQR(p->x) + SQR(p->y) + SQR(p->z));;
}

int FarthestPoint1(unsigned count, Point a, ...) {
    double max_distance = 0, curr_distance;
    int point_num = 1;
    Point* ppoint;
    for ( ppoint = &a; count > 0; count--, (char*)ppoint += 64) {
        curr_distance = CalcDistance(ppoint);
        if (curr_distance > max_distance) {
            max_distance = curr_distance;
            point_num = ((int)ppoint - (int)&a)/64 + 1;
        }
    }
    return point_num;
}

int FarthestPoint2(int num, Point a, ...) {
    double max_distance = CalcDistance(&a), curr_distance;
    int point_num = num, curr_num;
    Point point;
    va_list parg;
    va_start(parg, a);
    curr_num = va_arg(parg, int);
    while (curr_num != '#') {
        point = va_arg(parg, Point);
        curr_distance = CalcDistance(&point);
        if (curr_distance > max_distance) {
            max_distance = curr_distance;
            point_num = curr_num;
        }
        curr_num = va_arg(parg, int);
    }
    va_end(parg);

    return point_num;
}

12 Востаннє редагувалося Droid 77 (16.03.2023 22:20:48)

Re: Адресація параметрів функції

Пан автор теми.
Підозрюю що Ви не розумієте що таке стек, і як то працює.

13

Re: Адресація параметрів функції

Ну, зрозуміло звідки ноги ростуть..

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

https://i.ibb.co/X8H9DmM/image.png

Правда хто вирішив, що це працюватиме - питання відкрите.

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

https://i.ibb.co/WywQvTh/image.png

Foxy4 написав:

Якщо буде цікаво, то мій варіант №20.

Глянув на ваш варіант, але не побачив вимог, щоб не використовувати <stdarg.h>. Чи це ви про ось це:

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

https://i.ibb.co/bzVPP4Z/image.png

Але це вроді як не обов'язково. Тому раджу просто використовувати всюди <stdarg.h> і не паритись. А, для перевірки знань на вміння оперувати даними в оперативній пам'яті є нормальні завдання, а не ці збочення.

Подякували: Foxy4, leofun012

14

Re: Адресація параметрів функції

Droid 77 написав:

Пан автор теми.
Підозрюю що Ви не розумієте що таке стек, і як то працює.

Ну, власне він і вивчає саме для того, щоб розуміти.
Але тут точно варто основи асемблера розібрати.

15

Re: Адресація параметрів функції

koala написав:

Але тут точно варто основи асемблера розібрати.

На такій думці я й зупинився, зараз вивчати асемблер часу немає, тому чекаю 2 курс...