Чому адреси argc та argv 12 байтів один від одного?


40

Я запустив наступну програму на своєму комп’ютері (64-розрядний Intel, що працює під управлінням Linux).

#include <stdio.h>

void test(int argc, char **argv) {
    printf("[test] Argc Pointer: %p\n", &argc);
    printf("[test] Argv Pointer: %p\n", &argv);
}

int main(int argc, char **argv) {
    printf("Argc Pointer: %p\n", &argc);
    printf("Argv Pointer: %p\n", &argv);
    printf("Size of &argc: %lu\n", sizeof (&argc));
    printf("Size of &argv: %lu\n", sizeof (&argv));
    test(argc, argv);
    return 0;
}

Вихід програми був

$ gcc size.c -o size
$ ./size
Argc Pointer: 0x7fffd7000e4c
Argv Pointer: 0x7fffd7000e40
Size of &argc: 8
Size of &argv: 8
[test] Argc Pointer: 0x7fffd7000e2c
[test] Argv Pointer: 0x7fffd7000e20

Розмір вказівника &argv- 8 байт. Я очікував, що адреса argcбуде, address of (argv) + sizeof (argv) = 0x7ffed1a4c9f0 + 0x8 = 0x7ffed1a4c9f8але між ними є 4-байтне накладка. Чому це так?

Я здогадуюсь, що це може бути пов’язане з вирівнюванням пам’яті, але я не впевнений.

Я помічаю таку саму поведінку і з функціями, які я викликаю.


15
Чому ні? Вони можуть бути на відстані 174 байти. Відповідь залежатиме від вашої операційної системи та / або бібліотеки обгортки, для якої налаштовано main.
aschepler

2
@aschepler: Це не повинно залежати від будь-якої обгортки, для якої зроблено налаштування main. В C mainйого можна назвати як звичайну функцію, тому вона повинна отримувати аргументи, як звичайна функція, і повинна підкорятися ABI.
Eric Postpischil

@aschelper: Я помічаю таку ж поведінку і для інших функцій.
letmutx

4
Це цікавий «продуманий експеримент», але насправді, нічого, що повинно бути більше, ніж «я цікавлюсь чому». Ці адреси можуть змінюватися залежно від ОС, компілятора, версії компілятора, архітектури процесора і ні в якому разі не повинні залежати від "реального життя".
Ніл

Відповіді:


61

У вашій системі перші кілька цілочисельних чи вказівних аргументів передаються в регістри і не мають адреси. Коли ви приймаєте їхні адреси з &argcабо &argv, компілятор повинен сфабрикувати адреси, записавши вміст регістра для розташування стеків та надавши адреси цих місць стека. Роблячи це, компілятор вибирає, в певному сенсі, місця розташування стека, зручні для нього.


6
Зауважте, що це може статися, навіть якщо вони будуть передані на стек ; компілятор не зобов’язаний використовувати слот вхідних значень на стеку як сховище для локальних об'єктів, у які входять значення. Це може мати сенс робити це, оскільки функція в кінцевому підсумку переходить до хвостового виклику і потребує поточних значень цих об'єктів для створення вихідних аргументів для хвостового виклику.
R .. GitHub СТОП ДОПОМОГАЄТЬСЯ

10

Чому адреси argc та argv 12 байтів один від одного?

З точки зору мовного стандарту, відповідь - "немає конкретної причини". C не вказує та не передбачає будь-якого зв’язку між адресами параметрів функції. @EricPostpischil описує те, що, ймовірно, відбувається у вашій конкретній реалізації, але ці деталі будуть різними для реалізації, в якій всі аргументи передаються на стек, і це не єдина альтернатива.

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


7
@ R..GitHubSTOPHELPINGICE: Обчислення одного з іншого частково визначене, не чітко визначене. Стандарт C не є суворим щодо того, як здійснюється перетворення uintptr_t, і він, безумовно, не визначає зв'язків між адресами параметрів або тим, де передаються аргументи.
Eric Postpischil

6
@ R..GitHubSTOPHELPINGICE: Той факт, що ви можете обернутись, означає, що g (f (x)) = x, де x - покажчик, f - перетворення-покажчик-на-uintptr_t, а g - перетворення-uintptr_t-в -показник. Математично і логічно це не означає, що g (f (x) +4) = x + 4. Наприклад, якщо f (x) було x², а g (y) sqrt (y), то g (f (x)) = x (для реального негативного x), але g (f (x) +4) ≠ x + 4, загалом. У разі покажчиків, перетворення на адресу uintptr_tможе дати адресу у високих 24 бітах, а деякі біти аутентифікації - у низьких 8 біт. Потім додайте 4 просто накручує аутентифікацію; він не оновлюється…
Eric Postpischil

5
… Біти адреси. Або перетворення на uintptr_t може дати базову адресу у високих 16 біт і зсув у низьких 16 біт, і додавання 4 до низьких біт може перенести у високі біти, але масштабування неправильне (оскільки представлена ​​адреса не є база • 65536 + зміщення, але, скоріше, база • 64 + зміщення, як це було в деяких системах). Простіше кажучи, uintptr_tви отримуєте від конверсії - це не обов'язково проста адреса.
Eric Postpischil

4
@ R..GitHubSTOPHELPINGICE з мого читання стандарту, є лише слабка гарантія, яка (void *)(uintptr_t)(void *)pпорівняється з рівними (void *)p. Варто зазначити, що комітет прокоментував майже цю точну проблему, зробивши висновок, що "впровадження ... також може трактувати покажчики, що базуються на різному походженні, як чіткі, хоча вони є побітними ідентичними ".
Райан Авелла

5
@ R..GitHubSTOPHELPINGICE: Вибачте, я пропустив, що ви додавали значення, обчислене як дві різні uintptr_tконверсії адрес, а не різні покажчики або "відоме" відстань у байтах. Звичайно, це правда, але як це корисно? Це залишається вірним , що «є всі ще не визначений спосіб обчислити одну з цих покажчиків від іншого» як відповідь держав, але розрахунок не розраховується bвід aа обчислює bвід обох aі b, оскільки bповинні бути використані при відніманні для обчислення суми додавати. Обчислення одного від іншого не визначається.
Eric Postpischil
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.