што робіць C рабіць, калі ARGV тыпу акрамя гольца **

Я нічога не рабіў у C, і вырашыў, што было б выдатна, каб паспрабаваць змяніць ўверх тып ARGV ад паўкокс * да ИНТ, проста каб паглядзець, што адбудзецца. Я гэта пісаў:

#include 
#include 
int main(int argc, int  argv )
{
        printf("arg is %d \n", argv);
}

Я атрымліваю вельмі дзіўны выхад з гэтай праграмы. Кожны раз, калі я запускаю яго, з любымі аргумэнтамі я запускаю яго, здаецца, проста выплюнуць назад выпадковыя ліку ў мяне. Вось вынік:

[14:30:00][maksim]~/learnProg/cDance$ ./dink
arg is -2058142376 
[14:30:01][maksim]~/learnProg/cDance$ ./dink 2141
arg is 2111473256 
[14:30:04][maksim]~/learnProg/cDance$ ./dink 2141
arg is -8005928 

(Праграма называецца дзінка). Што адбываецца? Што C рабіць, калі ён кампілюе гэта? Што б адбылося, калі б я выкарыстаў тыпы дадзеных, выдатных ад міжнар, як двайны або структуры або што?

2
Не ведаю, чаму downvotes. Гэта вельмі добрае пытанне, OP даволі ясна, што яны ведаюць, як напісаць асноўную праграму C, а таксама некалькі вопытных распрацоўшчыкаў C я не ведаю, як апрацоўвае кампілятар або кампаноўнік гэта.
дададзена аўтар djechlin, крыніца
Паспрабуйце яшчэ раз прачытаўшы пытанне.
дададзена аўтар MYV, крыніца
Im гуляючы ад акадэмічнага цікавасці
дададзена аўтар MYV, крыніца
Я раблю гэта too..studying як сістэма паводзіць сябе ў дзіўных сітуацыях з'яўляецца добрым спосабам, каб стаць лепшым праграмістам. чаму ўсё так злы?
дададзена аўтар MYV, крыніца
не павінен Int ARGV будзе сімвал ** ARGV ? ideone.com/MENAsZ
дададзена аўтар Bill, крыніца
<�Код> ARGV гэта сімвал ** так што калі вы перадаеце ў якасці Int вы павінны кінуць яго назад. Месінга са стандартнымі сігнатурамі функцый не дасць вам чаканыя вынікі :)
дададзена аўтар Bill, крыніца
паспрабуйце кінуць яго (паўкокс **) і доступ да яго змесціва
дададзена аўтар maazza, крыніца
Чаму гуляць вакол, C, як правіла паказальнік на радок знакаў, ня канвертаваць easilly, вы можаце выбраць для Perl або іншай мовы, які робіць некаторыя магіі для вас.
дададзена аўтар hetepeperfan, крыніца
Чаму б вам не паспрабаваць яшчэ «цікавыя рэчы», як з дапамогай сістэмных выклікаў у дзіўных спосабах або псуючы біты ці нешта іншае, а затым спытаць, што C робіць у гэтым выпадку?
дададзена аўтар John Rivers, крыніца

7 адказы

Як ужо адзначалася іншыя, паводзіны не вызначана (так што ўсё можа здарыцца).

Давайце паглядзім на тры «тыповага» паводзін, хоць. Існуе тры асноўных спосабу перадачы аргументаў з'яўляюцца:

  • на стэк
  • у рэгістрах агульнага прызначэння
  • у рэгістрах спецыяльнага прызначэння

Сістэмы Intel x86 асноўным выкарыстоўваюць першы метад (але часам другі ці трэці). працэсары MIPS заснаваныя на асноўным выкарыстоўваюць другую.

Калі сістэма выкарыстоўвае адзін або некалькі чарак, звычайны метад выкліку з'яўляецца:

    <�Літый> у выклікалым (некаторыя OS пастаўлянымі працэдуру, якая выклікае Асноўны ), націсніце аргументы, як правіла, справа налева, гэта значыць у зваротным парадку. Стэк выштурхвае звычайна (але не заўсёды) выглядаць * - SP = значэнне ;. у C, з паказальнікам стэка (ов), які сыходзіў з некаторым высокім адрасы <�Літый> зрабіць выклік у мэтавай функцыі ( Асноўны ) </літый> <�Літый> у мэтавай функцыі, атрымаць параметры выкл "стэк» або «стэк параметраў» або «бягучы стэк ніткі» ці нешта сістэма выкарыстоўвае. Паколькі яны былі вылучаныя ў адваротным парадку, яны па адрасах, як SP [0] , зр [1] і г.д. Калі механізм выкліку выкарыстоўвае адзін і той жа стэк ў якасці параметру -passing механізм, індэксы могуць пачынацца з 1 або 2 ці нават больш ( зр [2] які з'яўляецца першым аргументам, напрыклад, і SP [3] з'яўляецца другім ).

У гэтым выпадку, ARGC , верагодна, выйдзе правільна, але ARGV будзе няправільна інтэрпрэтаваць тое, што выклікае штурхнуў, вырабляючы дзіўнага выгляду Int . Калі базавая сістэма дастаткова фантазіі (праверка тыпаў), ён можа выявіць, што выклікае абанент націснуў значэнне тыпу гольца ** , але вы звяртаецеся адзін з тыпу Int , і даць вам нейкую памылку часу выканання. Большасць сістэм проста аддаюць перавагу, каб даць вам няправільны адказ як мага хутчэй, хоць, прапускаючы праверку тыпаў. Такім чынам, вы атрымаеце дзіўнага выгляду Int , але гэта будзе на самой справе на аснове (прынамсі, часткова, см.ниже) ад фактычнага значэння паказальніка абанент спрабаваў перадаць.

If the system uses general purpose registers (instead of, or prior to, using a stack—systems using GPRs often fall back on stacks if you use many parameters, and sometimes use them for all variadic functions, i.e., those using the facilities), then the calling method looks more like this:

  • у выклікалым, перамяшчаць аргументы ( Int ARGC <�код /> значэнне і сімвал ** ARGV значэнне) у першых двух рэгістраў аргументаў (напрыклад, % о0 і % o1 на SPARC, або $ a0 і $ a1 на MIPS).
  • зрабіць выклік мэтавай функцыі
  • <�Літый> у мэтавай функцыі, доступ да значэнняў з рэгістраў аргументаў

У гэтым выпадку код наогул паводзіць сябе так жа, як у сістэме на аснове стэка. Ён проста працуе хутчэй, так як аргументы-в-рэгістраў, як правіла, маюць патрэбу ў меншай колькасці цыклаў працэсара, чым аргументы-в-памяці. (Вось чаму некаторыя кампілятары Intel часам перадаць аргумент ці два ў рэгістрах.)

Калі сістэма выкарыстоўвае спецыяльныя рэгістры прызначэння, аднак, мы атрымліваем новае бачнае паводзіны. Дапусцім, што значэнні з якая плавае кропкай ідуць у е рэгістры (праўда ў некаторых сістэмах SPARC; x86 мае MMX і SSE рэгістраў замест); значэння паказальніка перайсці ў а рэгістраў (а-ля 680x0 CPU); і цэлыя значэння ідуць у d рэгістры (680x0, зноў-такі, хоць на практыцы большасць 680x0 сістэмы проста выкарыстоўваць "стэк», але давайце выкажам здагадку, што ў нас ёсць той, які выкарыстоўвае рэгістры). На гэты раз справа выкліку Асноўны павінен перадаць адно цэлае лік, ARGC , і адзін паказальнік, ARGV , таму ён робіць гэта:

  • move integer argument argc into data register d0
  • move pointer argument argv into pointer register a0
  • call main

Цяпер, у Асноўны() , вы сказалі, кампілятар чакаць дзве лік аргументы, якія б прыбываюць у рэгістрах d0 і d1 адпаведна. Што ў рэгістры працэсара d1 ? Хто ведае, то, што называецца Асноўны не паставіў яго прама перад выклікам. Ён мае любое значэнне мае, у таго, хто ў апошні раз затрымаўся нейкае значэнне ў ім. Значэнне не больш не звязаная з вызначаным ARGV , так як гэта ў рэгістры a0 .

Цяпер, нават калі ў вас ёсць стэк або GPR-сістэма на аснове выкліку, ёсць яшчэ некалькі маршчын, каб разгледзець наступныя пытанні:

  • Што рабіць, калі паказальнікі 64 біт і звычайны Int s толькі 32 біта? У гэтым выпадку выклікае абанент націскае 64-бітнае значэнне, або запісвае 64-бітнае значэнне параметру ў рэгістр; але Асноўны выглядае толькі на 32 біта. Вы ўбачыце палову таго, што было на самай справе дадзена.
  • Што рабіць, калі паказальнікі 32 біт і звычайны Int ня 64 біта? Гэта незвычайная рэалізацыя, вядома, але цяпер вы будзеце глядзець на ўсіх 64 бітаў значэння, якое пастаўляецца толькі 32. «лішнія» 32 біта могуць быць усе роўныя нулю (гэта было б характэрна для параметраў GPRs), або можа быць 32 біта некаторай незвязанай значэння, аналагічныя нагоды праверкі рэгістра d1 пры Асноўны 'ы выклікае абанента запоўненыя ў рэгістры А0 .
  • І, вядома, няма нічога, што кажа, 32 і 64 біта з'яўляюцца адзіна магчымымі памерамі. На AS/400 сістэм IBM, паказальнікі на цэлых 128 біт даўжынёй 16 байт (заўважана паказальнікі), і існуе шырокае час выканання праверкі тыпаў. Гэтыя машыны працуюць на тым, што код з'яўляецца правільным, а не проста хутка.

Там яшчэ адна магчымасць адзначыць. Калі пабудаваць падобны C ++ код (з функцыяй акрамя Асноўны ), гэта наогул не атрымоўваецца ўсталяваць сувязь. Прычына заключаецца ў тым, што кампілятар C ++ часта выкарыстоўвае метад пад назвай «імя перакручваючы» для апрацоўкі перагружаных функцый. Функцыя з імем е , які прымае адзін Int і адзін сімвал ** аргумент і вяртае Int вырабляе стыкоўку час сімвал Z1fiPPC . Функцыя з імем е , які прымае два Int с і вяртае Int вырабляе сімвал спасылка часу Z1fii замест гэтага. Я не бачыў C кампілятары, якія робяць гэта, але яны можа гэта зрабіць. У гэтым выпадку кампілятар будзе правяраць, падчас кампаноўкі, ці з'яўляецца ваша праграма вызначаецца Z4mainippC - Int асноўных (INT, сімвал **) -І калі так, то спасылку ў абанент, які прадастаўляе гэтыя аргументы; або праверыць, будзе для Z4mainv - Int асноўны (анулююцца) -І ў гэтым выпадку спасылка ў выклікалай боку, не дае ніякіх аргументаў. Калі ні адна функцыя знойдзеная, линкер можа выявіць, што вы напісалі няправільны асноўны і не ствараць выкананы на ўсіх!

4
дададзена
Вы паходзіце, хто напісаў кампілятар. :-)
дададзена аўтар Jens, крыніца
@Jens: добра, «працаваў» (больш чым адзін), не зусім «напісана» ... кампілятараў гэтыя дні занадта вялікія. :-)
дададзена аўтар torek, крыніца

Вы атрымаеце нявызначаны паводзіны, што азначае, што гэта законна для што-небудзь адбудзецца. <�Код> Асноўны павінен быць абвешчаны як:

int main(void)

ці як:

int main(int arg, char** argv)

або ў той ці іншай форме вызначаецца вашай рэалізацыі.

З падзелу J.2 стандарту ISO C99:

<�Р> паводзіны не вызначана ў наступных выпадках: </р>      <�Р> ...      
      
  • Праграма ў асяроддзі хостынгу не вызначае функцыю з імем Асноўны , выкарыстоўваючы адзін з названых формаў (5.1.2.2.1).
  •   
3
дададзена
«Ці ў якой-небудзь іншай рэалізацыі пэўным чынам.» Калі дакументы аб ажыццяўленні яго, структура {INT Foo, бар; падвойная quux; без знака Баз: 3; } Галоўная (падвойная ARGC, несапраўдны *** ARGV) з'яўляецца законнай. Добра, ня вядомых дакументаў па рэалізацыі <�я>, што </я> подпіс, але Int асноўных (INT ARGC, сімвал ** ARGV, сімвал ** envp) ня нечуванае.
дададзена аўтар Daniel Fischer, крыніца
Нягледзячы на ​​тое, што гэта можа быць найменш інфарматыўны адказ, гэта ў пэўным сэнсе найбольш правільным. У агульным выпадку, па архітэктуры, рэалізацый і версій, гэта ўсё, што можна сказаць. Той факт, што мы можам разважаць пра тое, чаму кампілятар зрабіў тое, што ён зрабіў не мяняе фундаментальную ісціну, што вы бачыце на самай справе проста «смецце, смецце».
дададзена аўтар tripleee, крыніца

argv is passed to your program as a pointer to an array of pointers to strings.

Калі хлусня і кампілятара, што гэта элемент Int , байты паказальніка будзе інтэрпрэтавацца як Int , і вы атрымаеце адрас памяці. (На 64-бітнай сістэме, вам, верагодна, аварыі)

Калі вы робіце выгляд, што гэта паплавок , кампілятар можа інтэрпрэтаваць гэтыя байты/біт у якасці ПЭО-754-кадаванага значэння з якая плавае кропкай, у выніку чаго па-рознаму дзіўнае лік. (Што менавіта адбудзецца, залежыць ад выклікалай канвенцыі)

Калі вы робіце выгляд, што гэта любы тып, які не з'яўляецца такой жа шырыні, як паказальнік, вы, верагодна, аварыі.

Мараль гэтай гісторыі

C робіць менавіта тое, што вы сказаць яму. Гэта да вас, каб сказаць яму, як інтэрпрэтаваць рэчы.

2
дададзена
Хіба Вы не азначае, што ARGV у першым радку?
дададзена аўтар Grzegorz Piwowarek, крыніца
@pivovarit: Так; фіксаваны.
дададзена аўтар SLaks, крыніца
@EricPostpischil: Я згадаў адрозненне паказальніка памеру. Чаму б паплаўкі быць перададзены іншым? Для таго, каб лепш працаваць з ПДП?
дададзена аўтар SLaks, крыніца
Многія рэалізацыі C перадаваць ўказальнікі ў адных і тых жа месцах, як Int аргументы, але гэта не з'яўляецца незвычайным для аргументаў з якая плавае кропкай, якія перадаюцца ў розных месцах. Таму няправільна сцвярджаць, што аргумент, перадаваны ад выклікае абанента, які разглядае яго як паказальнік, але атрымалі падпраграму, якая разглядае яго як Int будзе інтэрпрэтаваць байты паказальніка як значэнне з якая плавае кропкай , Байты ў тым месцы, дзе праходзіць значэнне з якая плавае кропкай, як правіла, зусім не звязаныя з паказальнікам.
дададзена аўтар Eric Postpischil, крыніца
Акрамя таго, нават у рэалізацыях, якія праходзяць Int аргументы на тых жа месцах, у якасці аргументаў паказальнікаў, размяшчэнне байт, перададзенае для паказальніка не абавязкова адпавядаюць мясцінах для Int , бо не адпавядае памеру (напрыклад, 32-біт Int , і 64-бітны паказальнік) могуць змяніць, дзе перадаюцца канкрэтныя аргументы.
дададзена аўтар Eric Postpischil, крыніца
@SLaks: Так, гэта значыць агульная прычына для перадачы значэнняў з якая плавае коскі па-рознаму. Гэта займае час, каб перанесці значэння з рэгістраў з якая плавае кропкай у рэгістры або ў памяць, і патрабуецца час, каб перамясціць іх назад. Такім чынам, гэта адходы патрабуюць тэлефануюць, каб перамясціць дадзеныя замест таго, каб пакінуць яго ў рэгістрах, дзе яна часта ўжо ёсць і дзе ён, верагодна, будзе выкарыстоўвацца.
дададзена аўтар Eric Postpischil, крыніца

Давайце спачатку зразумець, што менавіта ARGV гэта.

Разгледзім стандартны асноўны) <�код /> фармат (. Гэта Int асноўных (INT ARGC, сімвал * ARGV []) Тут ARGV ўяўляе сабой масіў знакавых паказальнікаў. Так як імя масіва з'яўляецца пастаянным паказальнікам на яго першы элемент, мы скажам ARGV з'яўляецца паказальнікам на яго першы элемент. г.зн. ARGV гэта паказальнік на паказальнік знакаў.

Зараз звернеце ўвагу на імя тут не мае значэння. Гэта можа быць што заўгодна, акрамя ARGV . Важна, другі аргумент асноўны() з'яўляецца паказальнікам на паказальнік знакаў. г.зн. другі аргумент паказальнік на паказальнік на знак.

Таму, калі праграма пачынае выкананне, адрас памяці перадаюцца ў якасці другога аргументу ў асноўнага() , які з'яўляецца адрасам іншага паказальніка. І гэта яшчэ адзін "паказальнік адрас памяці вельмі першага знака першага аргументу. І гэты аргумент здараецца назва праграмы.

Таму, калі вы кажаце Int асноўны (ідэалам ARGC, унутр ARGV) вы кідаеце адрас у Int значэнне. Калі SizeOf (INT) == SizeOf (INT *) , то гэта не праблема. Значэнне не будзе паніжаны ў гэтым выпадку.

Зараз, калі вы кажаце Е ( «Arg з'яўляецца% d \ п», ARGV); вы проста друкаваць гэты адрас. Вось так! Незалежна ад таго, якія вашыя аргументы, прыведзеныя ў праграме, што адрас не з'яўляецца выпадковай велічынёй. Вось чаму вы атрымліваеце выпадковыя no.s, якія фактычна з'яўляюцца адрасы першага элемента ARGV масіў. г.зн. няма. надрукаваны з'яўляецца адрас імя праграмы, якая ў сваю чаргу з'яўляецца адрас яго першы знак. (Так як назва праграмы зноў масіў так пастаянны паказальнік на яго першы элемент. Гэта значыць, самага першага знака)

Каб праверыць гэта дадаць гэты радок да вас фрагмент кода:

printf("%c\n", **(char **)argv);  

You will see . being printed which was indeed the very first character of very first argument ./dink

0
дададзена

Ну ...

ARGV з'яўляецца масівам. У C масівы проста паказальнікі. Паказальнікі ўнутрана толькі цэлыя для вочак памяці. Такім чынам, лік вы бачылі, гэта месца ў памяці. (Я мяркую, негатывы, таму што гэта не без знака)

0
дададзена
Масівы не проста паказальнікі. Паказальнікі могуць або не могуць быць цэлымі лікамі для вочак памяці; што да кожнай рэалізацыі C.
дададзена аўтар Eric Postpischil, крыніца
Масівы і паказальнікі сапраўды адрозніваюцца, але фармальны параметр, напрыклад, ARGV у асноўным, калі абвешчаны з тыпам масіва, фактычна аб'яўляе паказальнік. (У C99, новы дадатковы сэнс статычным ключавое слова робіць трохі беспарадак старога правілы тыпу перапісвання, у імя аптымізацыі, але ў большасці выпадкаў могуць ігнараваць гэта.)
дададзена аўтар torek, крыніца

The C main() functions receives an integer for the argument count and a pointer to an array of char.

Ваш выснову проста адрас памяці, які гэты паказальнік змяшчае. Калі вы прыводзіце яго ў іншыя тыпы зменных, яны таксама будуць утрымліваць «смецце».

У нармальных умовах, варта пазбягаць, каб кінуць паказальнікі, калі гэта магчыма.

0
дададзена

Я не згодны з большасцю іншых адказаў і, як было адзначана jamesdlin, C99 вызначае паводзіны, як вызначана, калі Асноўны правільна не абвешчаная. Я думаю, то ваша пытанне будзе пра тое, што гэта так званае нявызначаны паводзіны. Я кажу "так званае нявызначаны паводзіны», таму што гэта, фактычна, вызначаецца вельмі дакладна, як частка платформы/сістэмы Application Binary Interface (ABI). Хоць ABI не можа канкрэтна вырашыць сітуацыю вы ставіце з перадаючы паказальнік як Int , але ён вызначае, як перадаюцца аргументы, і таму невялікае даследаванне пакажа, што менавіта адбываецца ў вашым канкрэтным выпадку.

Паколькі ABI адказы на ўсе пытанні аб тым, «што адбудзецца, калі я праходжу гэта як INT, двайныя або структуры», ваш наступны пытанне, можа быць «што з'яўляецца ABI для маёй сістэмы». ABI з'яўляецца сістэма/канкрэтнай платформы, яна можа адрознівацца ад Windows, і Linux, паміж PowerPC і X86, паміж рознымі кампілятарамі, і нават паміж рознымі версіямі кампілятара. Вы не далі неабходную платформу/сістэмы інфармацыі для адказу на «які ABI» пытанне, але, нават калі вы прадставілі яго, у мяне няма намеру адказваць на гэта, так як даследаванні спатрэбілася б з майго боку (я не эксперт) , Акрамя таго, гэта ваш вопыт, так што гэта будзе добры вопыт для вас, каб даследаваць і зразумець ABI вашай сістэмы.

Існуе шмат добрай інфармацыі там, у тым ліку пытанне з просьбай што такое ABI </а>, кароткі агляд Linux ABI і, вядома ж, вікіпедыя старонка . ABI пытанне дае спасылку на PDF System V ABI і што вельмі магчыма, пакрывае сістэма ABI так можа быць лепшым месцам для пачатку.

Падводзячы вынік, вашы вынікі эксперыменту ў непрадказальным паводзінах у адпаведнасці з C99, але фактычным паводзінамі вызначаюцца сістэмай ABI, але сістэма ABI, добра, канкрэтная сістэма. Іншымі словамі, C99 не вызначае паводзіны ў эксперыменце, таму што гэта паводзіны канкрэтнай сістэмы, якая знаходзіцца па-за межамі C99. Сістэма канкрэтнага ABI, з другога боку, не вызначае паводзіны як частка вызначэння таго, як перадаюцца аргументы. Разумеючы ABI вашай сістэмы, вы будзеце ў стане зразумець (гэта значыць вызначыць ) паводзіны вы бачыце. Хутчэй за ўсё, гэта вызначэнне будзе некалькі невпечатляющими, напрыклад, Int аргумент і паказальнік аргумент не сумяшчальныя з тым, што вы атрымліваеце, як Int аргумент сапраўды з'яўляецца выпадковым смеццем, што здараецца знаходзяцца ў пэўным рэгістры ці памяці месцазнаходжаньня. Ці гэта можа быць верхнім ці ніжнім 32бит паказальнік 64-бітнага.

0
дададзена