C pobiera główne argumenty funkcji. Główne argumenty funkcji. Używanie argumentów wiersza poleceń


Czasami przy uruchamianiu programu warto przekazać mu pewne informacje. Zwykle informacje te są przekazywane do funkcji main() za pośrednictwem argumentów wiersza poleceń. Argument wiersza poleceń to informacja wprowadzana w wierszu poleceń systemu operacyjnego po nazwie programu. Na przykład, aby rozpocząć kompilację programu, po znaku zachęty w wierszu poleceń należy wpisać coś podobnego do poniższego:

Dw Nazwa programu

Nazwa programu jest argumentem wiersza poleceń; określa nazwę programu, który zamierzasz skompilować.

Aby zaakceptować argumenty wiersza poleceń, używane są dwa specjalne wbudowane argumenty: argc i argv. Parametr argc zawiera liczbę argumentów w wierszu poleceń i jest liczbą całkowitą, przy czym zawsze wynosi co najmniej 1, ponieważ pierwszym argumentem jest nazwa programu. Parametr argv jest wskaźnikiem do tablicy wskaźników do ciągów znaków. W tej tablicy każdy element wskazuje na argument wiersza poleceń. Wszystkie argumenty wiersza poleceń są ciągami znaków, więc konwersja dowolnych liczb na żądany format binarny musi być zapewniona w programie podczas jego tworzenia.

Oto prosty przykład użycia argumentu wiersza poleceń. Na ekranie zostanie wyświetlone słowo Hello i Twoje imię, które należy podać jako argument wiersza poleceń.

#włączać #włączać int main(int argc, char *argv) ( if(argc!=2) ( printf("Zapomniałeś wpisać swoje imię.\n"); exit(1); ) printf("Witam %s", argv) ;zwróć 0; )

Jeśli nazwałeś ten program nazwą (nazwą) i masz na imię Tomek, to aby uruchomić program powinieneś wpisać nazwę Tom w wierszu poleceń. W wyniku uruchomienia programu na ekranie pojawi się komunikat Witaj, Tomku.

W wielu środowiskach wszystkie argumenty wiersza poleceń muszą być oddzielone spacją lub tabulatorem. Przecinki, średniki i podobne znaki nie są uważane za ograniczniki. Na przykład,

Biegnij Spot, biegnij

składa się z trzech ciągów znaków, natomiast

Eryk, Rick, Fred

reprezentuje pojedynczy ciąg znaków - przecinki z reguły nie są uważane za ograniczniki.

Jeśli ciąg zawiera spacje, w niektórych środowiskach można go ująć w cudzysłów, aby zapobiec tworzeniu wielu argumentów. W rezultacie cały ciąg zostanie uznany za jeden argument. Aby dowiedzieć się więcej o tym, jak system operacyjny ustawia parametry wiersza poleceń, przejrzyj dokumentację systemu operacyjnego.

Bardzo ważne jest prawidłowe zadeklarowanie argv. Oto jak najczęściej się to robi:

Char *argv;

Puste nawiasy kwadratowe wskazują, że tablica ma nieokreśloną długość. Dostęp do poszczególnych argumentów można teraz uzyskać poprzez indeksowanie tablicy argv. Na przykład argv wskazuje na pierwszy ciąg znaków, który zawsze jest nazwą programu; argv wskazuje na pierwszy argument i tak dalej.

Innym małym przykładem użycia argumentów wiersza poleceń jest następujący program odliczający. Program ten liczy wstecz, zaczynając od pewnej wartości (określonej w wierszu poleceń) i wydaje sygnał dźwiękowy, gdy osiągnie 0. Należy zauważyć, że pierwszy argument, zawierający wartość początkową, jest konwertowany na wartość całkowitą przy użyciu standardowej funkcji atoi (). Jeżeli drugim argumentem linii poleceń (a jeśli jako argument potraktujemy nazwę programu, to trzecim) będzie linia „display” (display), wówczas na ekranie zostanie wyświetlony wynik zliczenia (w odwrotnej kolejności).

/* Program do liczenia w odwrotnej kolejności. */ #włączać #włączać #włączać #włączać int main(int argc, char *argv) ( int disp, liczba; if(argc<2) { printf("В командной строке необходимо ввести число, с которого\n"); printf("начинается отсчет. Попробуйте снова.\n"); exit(1); } if(argc==3 && !strcmp(argv, "display")) disp = 1; else disp = 0; for(count=atoi(argv); count; --count) if(disp) printf("%d\n", count); putchar("\a"); /* здесь подается звуковой сигнал */ printf("Счет закончен"); return 0; }

Należy pamiętać, że jeśli argumenty wiersza poleceń nie zostaną określone, zostanie wyświetlony komunikat o błędzie. Programy pobierające argumenty wiersza poleceń często wykonują następujące czynności: Kiedy użytkownik uruchamia te programy bez wprowadzenia wymaganych informacji, wyświetlają instrukcje dotyczące prawidłowego określania argumentów.

Aby uzyskać dostęp do pojedynczego znaku jednego z argumentów wiersza poleceń, wprowadź drugi indeks w argv. Na przykład następujący program wypisuje znak po znaku wszystkie argumenty, z którymi został wywołany:

#włączać int main(int argc, char *argv) ( int t, i; for(t=0; t

Pamiętaj, że pierwszy indeks argv zapewnia dostęp do ciągu, a drugi indeks zapewnia dostęp do jego poszczególnych znaków.

Zwykle argc i argv służą do wydawania programowi początkowych poleceń, których będzie potrzebował podczas uruchamiania. Na przykład argumenty wiersza poleceń często określają informacje, takie jak nazwa pliku, opcja lub alternatywne zachowanie. Używanie argumentów wiersza poleceń nadaje programowi „profesjonalny wygląd” i ułatwia jego użycie w plikach wsadowych.

Nazwy argc i argv są tradycyjne, ale nie wymagane. Możesz wywołać te dwa parametry w funkcji main(), jakkolwiek chcesz. Ponadto niektóre kompilatory mogą obsługiwać dodatkowe argumenty funkcji main(), dlatego należy sprawdzić dokumentację kompilatora.

Jeśli program nie wymaga parametrów wiersza poleceń, najczęściej jawnie deklaruje się funkcję main() jako nie posiadającą parametrów. W tym przypadku na liście parametrów tej funkcji używane jest słowo kluczowe void.

Tagi: Opcje wiersza poleceń

Opcje wiersza poleceń

C jest językiem skompilowanym. Po złożeniu program jest plikiem wykonywalnym (nie rozważamy tworzenia bibliotek dynamicznych, sterowników itp.). Nasze programy są bardzo proste i nie zawierają bibliotek Runtime, dzięki czemu można je przenieść na komputer z tym samym systemem operacyjnym (i podobną architekturą) i tam uruchomić.

Program może akceptować parametry podczas uruchamiania. Są argumentami funkcji main. Ogólny widok funkcji głównej jest następujący

Pustka main(int argc, char **argv) (...)

Pierwszym argumentem argc jest liczba parametrów przekazanych do funkcji. Drugi argument to tablica ciągów znaków – same parametry. Ponieważ parametry funkcji mogą być dowolne, są one przekazywane jako ciągi znaków, a sam program musi je przeanalizować i przekonwertować na żądany typ.

Pierwszym argumentem (argv) jest zawsze nazwa programu. W tym przypadku nazwa wyświetlana jest w zależności od tego, skąd program został uruchomiony.

#włączać #włączać void main(int argc, char **argv) ( printf("%s", argv); )

Teraz nauczmy się trochę pracować z wierszem poleceń. Będzie to potrzebne do przekazania argumentów do naszego programu. Kombinacja klawiszy Win+R powoduje wyświetlenie okna Uruchom. Wpisz cmd, a otworzysz wiersz poleceń. Plik cmd.exe można także znaleźć, wyszukując go w menu Start. W systemach operacyjnych typu Unix można wywołać program terminalowy.

Nie będziemy uczyć się zbyt wielu poleceń. Tylko te, które są potrzebne do pracy.

Polecenie cd, standardowe dla wszystkich systemów operacyjnych, powoduje przejście do żądanego folderu. Istnieją dwie zastrzeżone nazwy - . (kropka) i.. (dwie kropki). Kropka to nazwa bieżącego folderu.

Nie idzie nigdzie

Dostęp do folderu nadrzędnego

Przejdź do folderu nadrzędnego

Aby przejść do żądanego, wpisz adres CD. Na przykład musisz przejść do systemu Windows do folderu C:\Windows\System32

Płyta C:\Windows\System32

W systemie Linux, jeśli chcesz przejść do folderu /var/mysql

CD /var/mysql

Jeżeli ścieżka zawiera spacje, jest ona zapisana w cudzysłowie

CD "D:\Docuents and Settings\Prolog"

Terminal posiada następujące przydatne funkcje: jeśli naciśniesz strzałkę w górę, pojawi się poprzednio wykonane polecenie. Jeśli naciśniesz klawisz Tab, terminal spróbuje dokończyć linię znanego mu polecenia lub uzupełni ścieżkę, przechodząc przez wszystkie foldery i pliki w bieżącym folderze.
Wpisz CD C:\
naciśnij Tab i zobacz co się stanie.

Kolejne ważne polecenie, dir w systemie Windows i ls w systemie Linux, wyświetla zawartość bieżącego folderu (folderu, w którym aktualnie się znajdujesz) na konsoli.

Twój program zwrócił swoją pełną nazwę. Przejdź do folderu, w którym znajduje się Twój program i sprawdź jego zawartość


Teraz, gdy już przeszliśmy do naszego folderu, możemy uruchomić nasz program. Aby to zrobić, wpisz jej imię.


Uwaga, nazwa uległa zmianie. Ponieważ program jest wywoływany z własnego folderu, wyświetlana jest nazwa względna. Zmieńmy teraz program i sprawmy, aby wypisał wszystkie argumenty. które jej dano.

#włączać #włączać void main(int argc, char **argv) ( int i; for (i = 0; i< argc; i++) { printf("%s\n", argv[i]); } }

Złóż projekt. Przed montażem upewnij się, że program jest zamknięty. Teraz wywołaj program, przekazując mu różne argumenty. Aby to zrobić, wpisz nazwę programu i argumenty oddzielone spacją


Napiszmy teraz program, który pobiera dwa argumenty liczbowe i wyświetla ich sumę

#włączać #włączać #włączać void main(int argc, char **argv) ( int a, b; if (argc != 3) ( printf("Błąd: znaleziono %d argumentów. Potrzebuje dokładnie 2", argc-1); exit(1); ) a = atoi(argv); b = atoi(argv); printf("%d", a + b); )

Zbierzmy się i zadzwońmy


Tak działa większość programów. Klikając na skrót wywołujesz program, do którego się odnosi. Większość programów akceptuje także różne argumenty. Na przykład możesz wywołać przeglądarkę Firefox z wiersza poleceń i przekazać argumenty
firefox.exe „www.mozilla.org” „site” i natychmiast otworzy witryny pod określonymi adresami w dwóch zakładkach.

Wiele standardowych poleceń ma również parametry. W Windows zwyczajowo zaczynają się od ukośnika, w Unixie od minusa lub dwóch minusów. Na przykład

Wyświetla tylko foldery, ale w terminalu Linux

Ls -l wyświetla listę wszystkich plików i folderów z atrybutami

Aby wyświetlić dodatkowe polecenia systemu Windows, wpisz pomoc w wierszu poleceń lub zapoznaj się z instrukcją (łatwo ją znaleźć w Internecie). W przypadku Linuksa poleceń i ich opcji jest znacznie więcej, a część z nich to niezależne języki programowania, dlatego warto poznać chociaż minimalny zestaw i ich opcje.

Borland C++ obsługuje trzy argumenty funkcji main(). Pierwsze dwa to tradycyjne argc i argv. Są to jedyne argumenty funkcji main() zdefiniowane w standardzie ANSI C. Umożliwiają przekazywanie do programu argumentów wiersza poleceń. Argumenty wiersza poleceń to informacje występujące po nazwie programu w wierszu poleceń systemu operacyjnego. Na przykład, gdy program jest kompilowany przy użyciu kompilatora liniowego Borland, zwykle wpisuje się go jako bcc Nazwa programu

Gdzie Nazwa programu to program, który należy skompilować. Nazwa programu jest przekazywana do kompilatora jako argument.

Parametr argc zawiera liczbę argumentów wiersza poleceń i jest liczbą całkowitą. Jest zawsze równa co najmniej 1, ponieważ nazwa programu kwalifikuje się jako pierwszy argument. Parametr argv jest wskaźnikiem do tablicy wskaźników znakowych. Każdy element tej tablicy wskazuje na argument wiersza poleceń. Wszystkie argumenty wiersza poleceń są ciągami znaków. Wszystkie liczby są konwertowane przez program do formatu wewnętrznego. Poniższy program wypisuje słowo „Hello”, po którym następuje nazwa użytkownika, jeśli zostanie wpisana bezpośrednio po nazwie programu:

#włączać

{
jeśli(argc!=2)
{
printf("Zapomniałeś wpisać swoje imię\n");
zwróć 1;
}
printf("Witam %s", argv);
zwróć 0;
}

Jeśli wywołasz tę nazwę programu, a nazwa użytkownika to Sergey, to aby uruchomić program, powinieneś wpisać:
imię Siergiej.
W wyniku działania programu wyświetli się:
„Witam, Siergiej”.

Argumenty wiersza poleceń muszą być oddzielone spacjami lub tabulatorami. Przecinki, średniki i podobne znaki nie są uważane za ograniczniki. Na przykład:

Składa się z trzech linii, podczas gdy

Herb, Rick, Fred

To jest jedna linia – przecinki nie są ogranicznikami.

Jeśli chcesz przekazać ciąg zawierający spacje lub tabulatory jako pojedynczy argument, musisz ująć go w cudzysłów. Na przykład jest to jeden z argumentów:

"to jest test"

Ważne jest, aby poprawnie zadeklarować argv. Najbardziej typową metodą jest:

Puste nawiasy wskazują, że tablica nie ma stałej długości. Dostęp do poszczególnych elementów można uzyskać za pomocą indeksowania argv. Na przykład argv wskazuje pierwszą linię, która zawsze zawiera nazwę programu. argv wskazuje następną linię i tak dalej.

Poniżej znajduje się mały przykład użycia argumentów wiersza poleceń. Odlicza od wartości określonej w wierszu poleceń i emituje sygnał, gdy osiągnie zero. Zauważ, że pierwszy argument zawiera liczbę przekonwertowaną na liczbę całkowitą przy użyciu standardowej funkcji atoi(). Jeżeli jako drugi argument występuje ciąg „display”, to na ekranie zostanie wyświetlony sam licznik.

/* program liczący */

#włączać
#włączać
#włączać
int main(int argc, char *argv)
{
int disp, liczba;
jeśli (argc<2)
{
printf("Musisz podać długość licznika\n");
printf("w wierszu poleceń. Spróbuj ponownie.\n");
zwróć 1;
}
if (argc==3 && !strcmp(argv,"wyświetlacz")) disp = 1;
w przeciwnym razie disp = 0;
for(count=atoi(argv); liczba; -liczba)
if (disp) printf("%d ", liczba);
printf("%c", "\a"); /* na większości komputerów jest to wywołanie */
zwróć 0;
}

Należy pamiętać, że jeśli nie zostaną określone żadne argumenty, pojawi się komunikat o błędzie. Jest to najbardziej typowe w przypadku programów, które używają argumentów wiersza poleceń do wydawania instrukcji, jeśli podjęto próbę uruchomienia programu bez poprawnych informacji.

Aby uzyskać dostęp do poszczególnych znaków wiersza poleceń, dodaj drugi indeks do argv. Na przykład następujący program wypisuje po jednym znaku wszystkie argumenty, z którymi został wywołany:

#włączać
int main(int argc, char *argv)
{
int t, i;
dla(t=0; t {
ja = 0;
podczas(argv[t][i])
{
printf("%c", argv[t][i]);
}
printf(" ");
}
zwróć 0;
}

Musimy pamiętać, że pierwszy indeks służy do dostępu do ciągu, a drugi do dostępu do znaku w ciągu.

Zazwyczaj do uzyskiwania poleceń źródłowych używa się argc i argv. Teoretycznie możliwe jest posiadanie do 32767 argumentów, ale większość systemów operacyjnych nie pozwala nawet się do tego zbliżyć. Zwykle te argumenty służą do określenia nazwy pliku lub opcji. Używanie argumentów wiersza poleceń nadaje programowi profesjonalny wygląd i umożliwia używanie programu w plikach wsadowych.

Jeśli dołączysz plik WILDARGS.OBJ dostarczony z Borland C++, możesz używać szablonów w argumentach typu *.EXE. (Borland C++ automatycznie obsługuje symbole wieloznaczne i odpowiednio zwiększa argc.) Na przykład, jeśli podłączysz plik WILDARGS.OBJ do następującego programu, wyświetli się informacja, ile plików odpowiada nazwie pliku określonej w wierszu poleceń:

/* Połącz ten program z WILDARGS.OBJ */

#włączać
int main(int argc, char *argv)
{
zarejestruj się w ja;
printf("%d plików pasuje do podanej nazwy\n", argc-1);
printf("Są to: ");
dla (i=1; tj printf("%s ", argv[i]);
zwróć 0;
}

Jeśli nazwiemy ten program WA, to uruchomimy go w następujący sposób, otrzymamy ilość plików z rozszerzeniem EXE oraz listę nazw tych plików:

Oprócz argc i argv, Borland C++ udostępnia także trzeci argument wiersza poleceń -env. Parametr env umożliwia programowi dostęp do informacji o środowisku systemu operacyjnego. Parametr env musi występować po argc i argv i jest zadeklarowany w następujący sposób:

Jak widać, env deklaruje się w taki sam sposób jak argv. Podobnie jak argv, jest to wskaźnik do tablicy ciągów. Każda linia jest ciągiem środowiskowym zdefiniowanym przez system operacyjny. Parametr env nie ma równoważnego parametru argc, który określa liczbę wierszy środowiska. Zamiast tego ostatnia linia środowiska ma wartość null. Poniższy program wypisuje wszystkie ciągi znaków środowiska aktualnie zdefiniowane w systemie operacyjnym:

/* ten program wyświetla wszystkie linie środowiska */

#włączać
int main(int argc, char *argv, char *env)
{
int t;
for(t=0; środowisko[t]/t++)
printf("%s\n", env[t]);
zwróć 0;
}

Należy pamiętać, że chociaż argc i argv nie są używane przez program, muszą znajdować się na liście parametrów. C nie zna nazw parametrów. Zamiast tego o ich użyciu decyduje kolejność deklarowania parametrów. W rzeczywistości możesz wywołać parametr, jak chcesz. Ponieważ argc, argv i env są nazwami tradycyjnymi, najlepiej jest ich nadal używać, aby każdy czytający program mógł od razu zrozumieć, że są to argumenty funkcji main().

Typowym zadaniem programów jest wyszukanie wartości zdefiniowanej w ciągu znaków środowiska. Na przykład zawartość linii PATH umożliwia programom korzystanie ze ścieżek wyszukiwania. Poniższy program demonstruje, jak znaleźć ciągi znaków deklarujące standardowe ścieżki wyszukiwania. Wykorzystuje standardową funkcję biblioteczną strstr(), która ma następujący prototyp:

Char *strstr(stała char *str1, stała char *str2);

Funkcja strstr() wyszukuje ciąg znaków wskazany przez str1 w ciągu znaków wskazanym przez str2. Jeżeli taki ciąg zostanie znaleziony, zwracany jest wskaźnik do pierwszej pozycji. Jeśli nie zostaną znalezione żadne dopasowania, funkcja zwraca wartość NULL.

/* program wyszukuje wśród ciągów środowiskowych linię zawierającą PATH */

#włączać
#włączać
int main (int argc, char *argv, char *env)
{
int t;
for(t=0; środowisko[t]; t++)
{
if(strstr(env[t], "ŚCIEŻKA"))
printf("%s\n", env[t]);
}
zwróć 0;
}

Zdarza się, że dane są przesyłane do programu z wiersza poleceń w momencie jego wywołania. Takie dane nazywane są argumentami wiersza poleceń. Wygląda to na przykład tak:

./a.out test.txt ls -lt /home/peter/

Tutaj wywoływane są programy a.out (z bieżącego katalogu) i ls (z tego samego katalogu określonego w zmiennej środowiskowej PATH). Pierwszy program z linii poleceń otrzymuje jedno słowo - test.txt, drugi - dwa: -lt i /home/peter/.

Jeśli program jest napisany w C, to po jego uruchomieniu sterowanie jest natychmiast przekazywane do funkcji main(), zatem to właśnie funkcja otrzymuje argumenty wiersza poleceń przypisane do jej zmiennych parametrów.

Poprzednio definiowaliśmy funkcję main() tak, jakby nie pobierała żadnych parametrów i nic nie zwracała. Tak naprawdę w języku C każda funkcja domyślnie (jeśli nie zdefiniowano nic innego) zwraca liczbę całkowitą. Możesz być tego pewien. Jeśli napiszesz kod w ten sposób:

main() ( printf („Cześć \N") ; zwróć 0; )

Wtedy podczas kompilacji nie pojawi się żadne ostrzeżenie ani błąd. To samo stanie się, jeśli napiszesz int main() . Dowodzi to, że funkcja domyślnie zwraca liczbę całkowitą, a nie nic (void). Chociaż to, co zwraca funkcja, zawsze można „przesłonić”, na przykład voidmain() lub float main() .

Podczas wywoływania programu z wiersza poleceń przekazywana jest do niego zawsze następująca para danych:

  1. liczba całkowita, wskazujący liczbę słów (elementów oddzielonych spacjami) w wierszu poleceń po wywołaniu,
  2. wskaźnik do tablicy ciągów, gdzie każda linia jest oddzielnym słowem z linii poleceń.

Pamiętaj, że liczy się także sama nazwa programu. Na przykład, jeśli wywołanie wygląda tak:

./a.out 12 temat 2

Wtedy pierwszy argument programu ma wartość 4, a tablica ciągów znaków jest zdefiniowana jako (./a.out", "12", "motyw", "2").

Zwróć uwagę na terminologię, istnieją tylko dwa argumenty programu (liczba i tablica), ale tyle argumentów wiersza poleceń, ile chcesz. Argumenty wiersza poleceń są „konwertowane” na argumenty programu (na argumenty funkcji main()).
Te dane (liczba i wskaźnik) są przekazywane do programu nawet wtedy, gdy zostanie on po prostu wywołany po nazwie, bez przekazywania mu czegokolwiek: ./a.out. W tym przypadku pierwszy argument ma wartość 1, a drugi wskazuje na tablicę składającą się tylko z jednej linii („./a.out”).

To, że dane są przekazywane do programu, nie oznacza, że ​​funkcja main() musi je zaakceptować. Jeśli funkcja main() jest zdefiniowana bez parametrów, nie jest możliwy dostęp do argumentów wiersza poleceń. Chociaż nic nie stoi na przeszkodzie, aby je przesłać. Nie będzie żadnego błędu.

Aby uzyskać dostęp do danych przekazanych do programu, należy je przypisać do zmiennych. Ponieważ argumenty są natychmiast przekazywane do funkcji main(), jej nagłówek powinien wyglądać następująco:
main (int n, char *arr)

Pierwsza zmienna (n) zawiera liczbę słów, a druga zmienna zawiera wskaźnik do tablicy ciągów znaków. Często drugi parametr jest zapisywany jako **arr . Jednak to jest to samo. Przypomnijmy, że sama tablica ciągów znaków zawiera wskaźniki do ciągów znaków jako swoje elementy. I przekazujemy do funkcji wskaźnik na pierwszy element tablicy. Okazuje się, że przekazujemy wskaźnik do wskaźnika, tj. **opr.

Ćwiczenia
Napisz taki program:

#włączać int main(int argc, char ** argv) ( int i; printf („%d \N", argc) ; dla (i= 0 ; tj< argc; i++ ) puts (argv[ i] ) ; }

Wyświetla liczbę słów w wierszu poleceń po wywołaniu i każde słowo w nowym wierszu. Wywołaj to bez argumentów wiersza poleceń i z argumentami.

W programie wykorzystaliśmy zmienne parametrów argc i argv. Zwyczajowo używa się tych nazw, ale w rzeczywistości mogą one być dowolne. Lepiej trzymać się tego standardu, aby Twoje programy były bardziej zrozumiałe nie tylko dla Ciebie, ale także dla innych programistów.

Praktyczne znaczenie przesyłania danych do programu

Jeśli masz jakiekolwiek doświadczenie z wierszem poleceń GNU/Linux, wiesz, że większość poleceń ma przełączniki i argumenty. Na przykład podczas przeglądania zawartości katalogów, kopiowania, przenoszenia, jako argumenty podawane są obiekty systemu plików, na których wykonywane jest polecenie. Funkcje jego realizacji określa się za pomocą kluczy. Na przykład w zespole

Cp -r ../les_1 ../les_101

cp to nazwa polecenia, -r to przełącznik, a ../les_1 i ../les_101 to argumenty polecenia.

Ogólnie rzecz biorąc, najczęściej adresy plików i „modyfikatory” (są to klucze) procesu wykonywania programu są przesyłane do programów po ich uruchomieniu.

Napiszmy program, który otwiera określone przez użytkownika w wierszu poleceń pliki do zapisu lub dodania i zapisuje (dodaje) tam te same informacje, które użytkownik wprowadza z klawiatury podczas wykonywania programu:

#włączać #włączać main (int argc, char ** argv) ( int i, ch; PLIK * f[ 5 ] ; if (argc< 3 || argc >7) (umieszcza ( „Nieprawidłowa liczba parametrów”) ; zwróć 1; ) if (strcmp (argv[ 1 ], "-w" ) != 0 && strcmp (argv[ 1 ], "-a" ) != 0 ) ( stawia ( „Pierwszym parametrem może być -w lub -a”) ; zwróć 2; ) dla (i= 0 ; tj< argc- 2 ; i++ ) { f[ i] = fopen (argv[ i+ 2 ] , argv[ 1 ] + 1 ) ; if (f[ i] == NULL) { printf („Nie można otworzyć pliku %s\n”, argv[ i+ 2 ] ) ; zwróć 3; ) ) while ((ch = getchar () ) != EOF) for (i= 0 ; i< argc- 2 ; i++ ) putc (ch, f[ i] ) ; for (i= 0 ; i < argc- 2 ; i++ ) fclose (f[ i] ) ; return 0 ; }

Objaśnienia do kodu:

  1. Tworzona jest tablica pięciu wskaźników plików. Dlatego możesz otworzyć nie więcej niż pięć plików jednocześnie. Wskaźnik pliku pierwszego pliku będzie przechowywany w elemencie tablicy f, drugiego w elemencie f itd.
  2. Sprawdzana jest liczba argumentów wiersza poleceń. Powinno ich być co najmniej trzech, bo... pierwsza to nazwa programu, druga to tryb otwierania pliku, trzecia to pierwszy lub jedyny plik, do którego ma zostać zapisany. Ponieważ program pozwala otworzyć tylko pięć plików, łączna liczba argumentów wiersza poleceń nie może przekraczać siedmiu. Dlatego jeśli liczba argumentów jest mniejsza niż 3 lub większa niż 7, wówczas program kończy się, ponieważ Instrukcja return powoduje zakończenie funkcji, nawet jeśli po niej znajduje się więcej kodu. Wartość zwrócona przez funkcję, która nie jest równa 0, może zostać zinterpretowana przez proces nadrzędny jako komunikat, że program zakończył działanie z błędem.
  3. Sprawdza ważność drugiego argumentu wiersza poleceń. Jeśli nie jest to ani „-w”, ani „-a”, wówczas wyrażenie warunkowe w drugim przypadku zwraca 1 (prawda). Funkcja strcmp() umożliwia porównywanie ciągów znaków i zwraca 0, jeśli są równe.
  4. Pętla for otwiera pliki pod określonymi adresami, które zaczynają się od trzeciego elementu tablicy argv. Dlatego do i dodaje się 2, aby uzyskać elementy tablicy argv, zaczynając od trzeciego. Wyrażenie argc-2 wskazuje liczbę przekazanych nazw plików; ponieważ argc przechowuje całkowitą liczbę argumentów wiersza poleceń, z których pierwsze dwa nie są nazwami plików.
  5. Wyrażenie argv+1 pozwala „wyciąć” podciąg „w” (lub „a”) z ciągu „-w” (lub „-a”), ponieważ argv jest zasadniczo wskaźnikiem do pierwszego elementu ciągu. Dodając jedynkę do wskaźnika, przenosimy go do kolejnego elementu tablicy.
  6. Jeśli nie można otworzyć pliku, funkcja fopen() zwraca NULL. W takim przypadku program się kończy.
  7. Każdy znak wprowadzony przez użytkownika na klawiaturze jest zapisywany do wszystkich otwartych plików.
  8. Na koniec pliki są zamykane.

Któregoś dnia zainteresowałem się zawartością stosu głównej funkcji procesu w Linuksie. Zrobiłem trochę badań i teraz przedstawiam wam wynik.

Opcje opisu funkcji głównej:
1. int główna()
2. int main(int argc, char **argv)
3. int main(int argc, char **argv, char **env)
4. int main(int argc, char **argv, char **env, ElfW(auxv_t) auxv)
5. int main(int argc, char **argv, char **env, char **jabłko)

Argc - liczba parametrów
argv - tablica wskaźników zerowych do ciągów parametrów wiersza poleceń
env to kończąca się zerowo tablica wskaźników do ciągów zmiennych środowiskowych. Każda linia w formacie NAZWA=WARTOŚĆ
auxv - tablica wartości pomocniczych (dostępna tylko dla PowerPC)
apple - ścieżka do pliku wykonywalnego (na MacOS i Darwin)
Wektor pomocniczy to tablica zawierająca różne dodatkowe informacje, takie jak efektywny identyfikator użytkownika, atrybut bitowy setuid, rozmiar strony pamięci itp.

Rozmiar segmentu stosu można znaleźć w pliku map:
kot /proc/10918/maps

7ffffffa3000-7ffffffff000 rw-p 00000000 00:00 0

Zanim moduł ładujący przekaże kontrolę do main, inicjuje zawartość tablic parametrów wiersza poleceń, zmiennych środowiskowych i wektora pomocniczego.
Po inicjalizacji góra stosu wygląda mniej więcej tak dla wersji 64-bitowej.
Adres seniora na górze.

1. 0x7ffffffff000 Górny punkt segmentu stosu. Wywołanie powoduje błąd seg
0x7ffffffff0f8 ZERO próżnia* 8 0x00"
2. Nazwa pliku zwęglać 1+ "/tmp/a.out"
zwęglać 1 0x00
...
śr zwęglać 1 0x00
...
zwęglać 1 0x00
3. 0x7ffffffffe5e0 śr zwęglać 1 ..
zwęglać 1 0x00
...
argumentacja zwęglać 1 0x00
...
zwęglać 1 0x00
4. 0x7ffffffffe5be argumentacja zwęglać 1+ "/tmp/a.out"
5. Tablica o losowej długości
6. dane dla auxv próżnia* 48"
AT_NULL Elf64_auxv_t 16 {0,0}
...
auxv Elf64_auxv_t 16
7. auxv Elf64_auxv_t 16 Np.: (0x0e,0x3e8)
ZERO próżnia* 8 0x00
...
śr zwęglać* 8
8. 0x7ffffffffe308 śr zwęglać* 8 0x7ffffffffe5e0
ZERO próżnia* 8 0x00
...
argumentacja zwęglać* 8
9. 0x7ffffffffe2f8 argumentacja zwęglać* 8 0x7ffffffffe5be
10. 0x7ffffffffe2f0 argc długi wew 8" liczba argumentów + 1
11. Zmienne lokalne i argumenty funkcji wywoływanych przed funkcją main
12. Zmienne lokalne główne
13. 0x7ffffffffe1fc argc wew 4 liczba argumentów + 1
0x7ffffffffe1f0 argumentacja zwęglać** 8 0x7ffffffffe2f8
0x7ffffffffe1e8 śr zwęglać** 8 0x7ffffffffe308
14. Lokalne zmienne funkcyjne

" - Nie znalazłem opisów pól w dokumentach, ale są one wyraźnie widoczne w zrzucie.

Nie sprawdzałem, czy są 32 bity, ale najprawdopodobniej wystarczy podzielić rozmiary przez dwa.

1. Dostęp do adresów powyżej górnego punktu powoduje błąd Segfault.
2. Ciąg zawierający ścieżkę do pliku wykonywalnego.
3. Tablica stringów ze zmiennymi środowiskowymi
4. Tablica ciągów znaków z parametrami wiersza poleceń
5. Tablica o losowej długości. Jego wybór można wyłączyć za pomocą poleceń
sysctl -w kernel.randomize_va_space=0
echo 0 > /proc/sys/kernel/randomize_va_space
6. Dane dla wektora pomocniczego (na przykład ciąg „x86_64”)
7. Wektor pomocniczy. Więcej szczegółów poniżej.
8. Zerowa tablica wskaźników do ciągów zmiennych środowiskowych
9. Tablica wskaźników z terminalem zerowym do ciągów parametrów wiersza poleceń
10. Słowo maszynowe zawierające liczbę parametrów wiersza poleceń (jeden z argumentów funkcji „głównych”, patrz akapit 11)
11. Zmienne lokalne i argumenty funkcji wywoływanych przed main(_start,__libc_start_main..)
12. Zmienne zadeklarowane w main
13.Argumenty funkcji głównej
14. Zmienne i argumenty funkcji lokalnych.

Wektor pomocniczy
Dla i386 i x86_64 nie jest możliwe uzyskanie adresu pierwszego elementu wektora pomocniczego, ale zawartość tego wektora można uzyskać w inny sposób. Jednym z nich jest dostęp do obszaru pamięci znajdującego się bezpośrednio za tablicą wskaźników do ciągów zmiennych środowiskowych.
Powinno to wyglądać mniej więcej tak:
#włączać #włączać int main(int argc, char** argv, char** env)( Elf64_auxv_t *auxv; //x86_64 // Elf32_auxv_t *auxv; //i386 while(*env++ != NULL); //szukam początku wektor pomocniczy dla ( auxv = (Elf64_auxv_t *)env; auxv->a_type != AT_NULL; auxv++)( printf("addr: %p typ: %lx to: 0x%lx\n", auxv, auxv->a_type, auxv->a_un .a_val); ) printf("\n (void*)(*argv) - (void*)auxv= %p - %p = %ld\n (void*)(argv)-(void* )(&auxv) =%p-%p = %ld\n ", (void*)(*argv), (void*)auxv, (void*)(*argv) - (void*)auxv, (void* )(argv) , (void*)(&auxv), (void*)(argv) - (void*)(&auxv)); printf("\n argc copy: %d\n",*((int *) (argv - 1 ))); zwróć 0; )
Struktury Elf(32,64)_auxv_t są opisane w /usr/include/elf.h. Funkcje wypełniania struktur w linux-kernel/fs/binfmt_elf.c

Drugi sposób uzyskania zawartości wektora:
hexdump /proc/self/auxv

Najbardziej czytelną reprezentację uzyskuje się poprzez ustawienie zmiennej środowiskowej LD_SHOW_AUXV.

LD_SHOW_AUXV=1 lz
AT_HWCAP: bfebfbff //możliwości procesora
AT_PAGESZ: 4096 //rozmiar strony pamięci
AT_CLKTCK: 100 //częstotliwość aktualizacji razy()
AT_PHDR: 0x400040 //informacje nagłówka
AT_PHENT: 56
AT_PHNUM: 9
AT_BASE: 0x7fd00b5bc000 //adres interpretera, czyli ld.so
AT_FLAGS: 0x0
AT_ENTRY: 0x402490 //punkt wejścia programu
AT_UID: 1000 //identyfikatory użytkowników i grup
AT_EUID: 1000 //nominalny i skuteczny
AT_GID: 1000
AT_EGID: 1000
AT_SECURE: 0 //czy podniesiona jest flaga setuid
AT_RANDOM: 0x7fff30bdc809 //adres 16 losowych bajtów,
generowane przy uruchomieniu
AT_SYSINFO_EHDR: 0x7fff30bff000 //wskaźnik do strony używanej
//wywołania systemowe
AT_EXECFN: /bin/ls
NA PLATFORMIE: x86_64
Po lewej stronie znajduje się nazwa zmiennej, po prawej wartość. Wszystkie możliwe nazwy zmiennych i ich opisy znajdziesz w pliku elf.h. (stałe z przedrostkiem AT_)

Powrót z głównego()
Po zainicjowaniu kontekstu procesu sterowanie jest przekazywane nie do funkcji main(), ale do funkcji _start().
main() została już wywołana z __libc_start_main. Ta ostatnia funkcja ma ciekawą cechę - przekazuje wskaźnik do funkcji, która powinna zostać wykonana po funkcji main(). Wskaźnik ten jest w naturalny sposób przekazywany przez stos.
Ogólnie argumenty __libc_start_main wyglądają tak, zgodnie z plikiem glibc-2.11/sysdeps/ia64/elf/start.S
/*
* Argumenty za __libc_start_main:
* out0: główne
* out1: argc
* out2: argv
* out3:inicj
* out4: fini //funkcja wywoływana po main
* out5: rtld_fini
* out6: koniec_stosu
*/
Te. aby uzyskać adres wskaźnika fini, musisz przesunąć dwa słowa maszynowe z ostatniej zmiennej lokalnej main.
Oto, co się stało (wykonalność zależy od wersji kompilatora):
#włączać nieważne **ret; unieważnij *opuść; void foo())( void (*boo)(void); //wskaźnik funkcji printf("Przepisanie stosu!\n"); boo = (void (*)(void))lee; boo(); // fini () ) int main(int argc, char *argv, char *envp) ( unsigned long int mark = 0xbfbfbfbfbfbfbfbf; //zaznacz, od którego będziemy pracować ret = (void**)(&mark+2); // wyodrębnij adres , funkcja wywoływana po zakończeniu (fini) Leave = *ret; // pamiętaj *ret = (void*)foo; // grind return 0; // wywołaj funkcję foo() )

Mam nadzieję, że było ciekawie.
Powodzenia.

Dziękujemy użytkownikowi Xeor za przydatną wskazówkę.

Kontynuując temat:
Routery

Większość klientów poczty e-mail, w tym Gmail, Mail.ru, Microsoft Outlook, Mozilla Thunderbird, umożliwia umieszczenie wielu odbiorców w DW (w języku angielskim...