Dlaczego argc nie jest stałą?

104
int main( const int argc , const char[] const argv)

Ponieważ w efektywnym punkcie C ++ nr 3 napisano: „Używaj stałej zawsze, gdy jest to możliwe”, zaczynam się zastanawiać „dlaczego nie uczynić tych„ stałych ”parametrów const?

Czy istnieje scenariusz, w którym wartość argcjest modyfikowana w programie?

Dinushan
źródło
38
Możesz zadeklarować argcjako const.
Oliver Charlesworth
14
Widziałem wiele kodów jakości produkcji, które to robią:--argc
Aniket Inge
2
Myślę, że ma to związek z dziedzictwem C, ale nie wiedziałbym jak.
Luis Machuca
13
Podejrzewam, że „Używaj stałej, gdy tylko jest to możliwe” odnosi się do rzeczy przekazanych przez odniesienie, gdzie każda modyfikacja w funkcji utrzymuje się po jej zakończeniu. Nie jest to prawdą w przypadku rzeczy przekazywanych przez wartość, takich jak liczba całkowita, więc nie można nic zyskać, tworząc ją const; faktycznie, przekazanie argcjako const intśrodek, którego nie można następnie użyć argcjako, powiedzmy, licznika wewnątrz funkcji.
Steve Melnikoff
4
@SteveMelnikoff: Nie zgadzam się, że nie ma nic do zyskania, tworząc constparametr przekazujący wartość. Patrz np stackoverflow.com/a/8714278/277304 i stackoverflow.com/a/117557/277304
leonbloy

Odpowiedzi:

114

W tym przypadku liczy się historia. C zdefiniował te dane wejściowe jako „niestałe”, a zgodność z (znaczną częścią) istniejącego kodu C była wczesnym celem C ++.

Niektóre interfejsy API UNIX, takie jak getopt, faktycznie manipulują argv[], więc nie można tego zrobić również constz tego powodu.

(Na bok: Co ciekawe, chociaż getoptprototyp sugeruje, że nie modyfikuje, argv[]ale może modyfikować wskazywane łańcuchy, strona podręcznika Linux wskazuje, że getoptpermutuje swoje argumenty i wygląda na to, że wiedzą, że są niegrzeczni . Strona podręcznika podręcznika Open Grupa nie wspomina o tej permutacji).

Umieszczenie constna argci argvnie kupić dużo, i byłoby unieważnienie trochę starej szkoły programowania praktyki, takie jak:

// print out all the arguments:
while (--argc)
    std::cout << *++argv << std::endl;

Pisałem takie programy w C i wiem, że nie jestem sam. Skopiowałem przykład skądś .

Joe Z
źródło
1
-1 Re "Niektóre UNIX-owe API, takie jak getopt, faktycznie manipulują argv [], więc nie można go ustawić jako const.". Można swobodnie dodawać najwyższego poziomu constdo argv, bez wpływu na to, co każdy może zrobić funkcja C. Ponadto getoptjest deklarowany jako int getopt(int argc, char * const argv[], const char *optstring);. I tutaj constnie jest to najwyższy poziom, ale deklaruje, że wskaźniki są const, obietnica ich nie modyfikowania (chociaż łańcuchy, na które wskazują, mogą zostać zmodyfikowane).
Pozdrawiam i hth. - Alf
2
Przepraszam, dokumenty, które przeglądałem, są niespójne: podpis nie pozwala na taką permutację. Zobacz przykład . Jednak poniższy tekst mówi, że tak jest. Więc wycofuję głos przeciw.
Pozdrawiam i hth. - Alf
1
Oznacza to, że będziesz musiał dokonać edycji, aby „odblokować”, abym mógł usunąć głos przeciw. I nie, źle rozumiesz prototyp. Spójrz na podany przeze mnie przykład i błąd kompilacji „przypisanie lokalizacji tylko do odczytu”.
Pozdrawiam i hth. - Alf
1
@ Cheersandhth.-Alf: To interesujące ... Wyszukałem prototyp w nagłówku i char* const* ___argvtam jest, ale interfejs faktycznie do niego pasuje const char **. Takie zamieszanie. Pomyliłem pierwszeństwo []i w *odniesieniu do const. Przepraszam.
Joe Z
1
FYI: GNU getopt()wie, że nie przestrzega standardu, ponieważ zwraca uwagę na POSIXLY_CORRECTzmienną środowiskową, aby działała poprawnie. W szczególności POSIX nie przesuwa argumentów i nie szuka opcji po pierwszym argumencie niebędącym opcją.
Jonathan Leffler,
36

Norma C (ISO / IEC 9899: 2011) mówi:

5.1.2.2.1 Uruchomienie programu

¶1 Nazwa funkcji wywoływanej podczas uruchamiania programu main. Implementacja nie deklaruje żadnego prototypu dla tej funkcji. Powinien być zdefiniowany za pomocą zwracanego typu int i bez parametrów:

int main(void) { /* ... */ }

lub z dwoma parametrami (określanymi tutaj jako argci argv, chociaż można używać dowolnych nazw, ponieważ są one lokalne dla funkcji, w której są zadeklarowane):

int main(int argc, char *argv[]) { /* ... */ }

lub odpowiednik; 10) lub w inny sposób określony w implementacji.

¶2 Jeśli są zadeklarowane, parametry mainfunkcji muszą spełniać następujące ograniczenia:

  • Wartość argcnie jest ujemna.
  • argv[argc] będzie pustym wskaźnikiem.
  • Jeśli wartość argcjest większa od zera, elementy składowe tablicy argv[0]za pośrednictwem argv[argc-1]włącznie powinny zawierać wskaźniki do ciągów, którym środowisko hosta nadaje wartości zdefiniowane w ramach implementacji przed uruchomieniem programu. Celem jest dostarczenie do programu informacji określonych przed uruchomieniem programu z innego miejsca w hostowanym środowisku. Jeśli środowisko hosta nie jest w stanie dostarczyć ciągów zawierających zarówno wielkie, jak i małe litery, implementacja powinna zapewnić, że łańcuchy są odbierane małymi literami.
  • Jeśli wartość argcjest większa od zera, ciąg wskazywany przez argv[0] reprezentuje nazwę programu; argv[0][0]będzie znakiem pustym, jeśli nazwa programu nie jest dostępna w środowisku hosta. Jeśli wartość argcjest większa niż jeden, ciągi wskazywane przez argv[1]przez argv[argc-1] reprezentują parametry programu.
  • Parametry argci argvoraz łańcuchy wskazywane przez argvtablicę mogą być modyfikowane przez program i zachowują swoje ostatnio zapisane wartości między uruchomieniem programu a zakończeniem programu.

10) W ten sposób intmożna go zastąpić nazwą typu zdefiniowanego jako int, lub typ argvmożna zapisać jako char **argvi tak dalej.

Zwróć uwagę na ostatni punkt. Mówi, że oba argci argvpowinny być modyfikowalne. Nie trzeba ich modyfikować, ale można je modyfikować.

Jonathan Leffler
źródło
Ciekawy. Jeśli chodzi o częściowo pokrewną notatkę, natknąłem się na błąd powodujący awarię, który okazał się być spowodowany tym, że QApplication(argc,argv)konstruktor Qt został zdefiniowany argcprzez odniesienie . To mnie zaskoczyło.
HostileFork mówi, że nie ufaj SE,
23

argczwykle nie jest stałą, ponieważ sygnatura funkcji dla main()dat poprzedzających const.

Ponieważ argc jest zmienną stosową, zmiana jej nie wpłynie na nic poza twoim własnym przetwarzaniem w linii poleceń.

Oczywiście możesz to zadeklarować, constjeśli chcesz.

razeh
źródło
8

Najwyższy poziom constargumentu formalnego nie jest częścią typu funkcji. Możesz go dodać lub usunąć, jak chcesz: wpływa tylko na to, co możesz zrobić z argumentem w implementacji funkcji.

Więc dla argcmożna dodać dowolnie dodać const.

Ale argvnie możesz zrobić danych znaku constbez zmiany sygnatury funkcji. Co oznacza, że ​​nie jest to jeden ze standardowych mainsygnatur funkcji i nie będzie musiał być rozpoznawany jako mainfunkcja. Więc to nie jest dobry pomysł.


Dobrym powodem niestosowania mainargumentów standardowych w programach innych niż zabawki jest to, że w systemie Windows nie są one w stanie przedstawić rzeczywistych argumentów programów, takich jak nazwy plików ze znakami międzynarodowymi. Dzieje się tak dlatego, że w systemie Windows są one według bardzo silnej konwencji kodowane jako Windows ANSI. W systemie Windows można zaimplementować bardziej przenośne narzędzie dostępu do argumentów w zakresie GetCommandLinefunkcji API.


Podsumowując, nic nie stoi na przeszkodzie, constaby dodać do argc, ale najbardziej użyteczna constfunkcja argvdaje niestandardową mainfunkcję, prawdopodobnie nie rozpoznawaną jako taką. Na szczęście (w ironiczny sposób) istnieją dobre powody, aby nie używać standardowych mainargumentów dla przenośnego, poważnego kodu. Po prostu, w praktyce obsługują tylko stare ASCII, tylko z literami alfabetu angielskiego.

Pozdrawiam i hth. - Alf
źródło
1
Jeśli tworzysz dla systemu Windows z cygwin lub inną biblioteką libc, która poprawnie obsługuje UTF-8 (o ile wiem, cygwin jest obecnie jedynym, ale mam kogoś, kto pracuje nad inną opcją), standardowy mainpodpis działa dobrze i możesz otrzymywać dowolne argumenty Unicode.
R .. GitHub PRZESTAŃ POMÓC W LODZIE
@R działają również dobrze na większości platform * nix. nie chodzi o to, czy są platformy, na których działają. ale bardzo fajna inicjatywa i tak się składa, że ​​robię to samo, mniej lub bardziej poważnie
Pozdrawiam i hth. - Alf
4

Podpis mainjest czymś w rodzaju historycznego artefaktu z C. Historycznie Cnie miał const.

Możesz jednak zadeklarować swój parametr, constponieważ efekty const dotyczą tylko czasu kompilacji.

Jerzy
źródło
Kiedy mówisz „historyczne C”, jak historycznie mówimy tutaj?
Joe Z
8
constzostał dodany do C89 / C90; wcześniej nie był częścią C.
Jonathan Leffler
@JonathanLeffler: Wiesz, zapomniałem o tym. Nauczyłem się C w 1992 roku. Wcześniej byłem Pascalem, BASICem i hakerem assemblera.
Joe Z
2

Ponieważ argcjest to zmienna lokalna (aw C ++ nie jest to odniesienie ani coś w tym rodzaju), a specjalne miejsce mainoznacza, że ​​shenanigans wstecznej kompatybilności daje jej ogromną swobodę bez żadnego istotnego powodu, aby wymusić na aplikacjach, aby stała się stała.

main() {}

int main() {}

main() { return 0; }

main(int argc, char* argv[]) { return 0; }

int main(const int argc, const char** argv) { /* no return*/ }

te i wiele innych odmian będzie kompilowanych na wielu kompilatorach C i C ++.

Więc ostatecznie nie chodzi o to, że argc nie jest const, po prostu nie musi tak być, ale może tak być, jeśli chcesz, aby tak było.

http://ideone.com/FKldHF , przykład w C:

main(const int argc, const char* argv[]) { return 0; }

http://ideone.com/m1qc9c , przykład w C ++

main(const int argc) {}
kfsone
źródło
Należy zauważyć, że warianty bez jawnego typu zwracanego nie są już prawidłowe w C99, nie mówiąc już o C11, chociaż kompilatory nadal zezwalają na nie ze względu na kompatybilność wsteczną. Kompilatory C ++ nie powinny akceptować wariantów bez zwracanego typu.
Jonathan Leffler
@JonathanLeffler Tak, ale ostatnim przykładem ideone ( ideone.com/m1qc9c ) jest G ++ 4.8.2 z -std=c++11. Clang również go akceptuje, ale daje warning: only one parameter on 'main' declaration [-Wmain], a MSVC protestuje tylko o brakującym specyfikatorze typu zwracanego i braku odniesienia do argc. Ponownie, 'shenanigans' i 'leeway' :)
kfsone
1

Oprócz powodów historycznych, dobrym powodem, aby zachować argumenty argc i argv non-, constjest to, że implementacja kompilatora nie wie, co zrobisz z argumentami main, po prostu wie, że musi podać te argumenty.

Definiując własne funkcje i skojarzone z nimi prototypy, wiesz, jakie parametry możesz wprowadzić, consta które funkcja będzie modyfikować.

W skrajnym przypadku można zadeklarować, że wszystkie parametry wszystkich funkcji powinny być zadeklarowane const, a wtedy gdybyś miał powód, aby je zmienić (np. Zmniejszyć indeks, aby przeszukać tablicę), musiałbyś utworzyć lokalne constzmienne niezmienne i skopiuj wartości constargumentów do tych zmiennych. To sprawia, że ​​praca jest zajęta i ma dodatkowe LOC, bez realnych korzyści. Przyzwoity analizator statyczny podniesie się, jeśli nie zmodyfikujesz wartości argumentu i zalecisz utworzenie parametru const.

Sam Skuce
źródło