Jeśli tablica ma być tablicą (ciągłym blokiem pamięci) to możesz zaalokować ją sobie dynamicznie (malloc, calloc), a potem realokować o kolejny blok jeśli zabraknie miejsca (realloc), ale to jest kosztowne. Innym rozwiązaniem jest lista powiązana, którą później składasz do kupy, lecz dla wartości typu char linked list to lekki overkill.
Jeśli nie potrzebujesz ciągłego bloku pamięci, możesz spróbować zrobić listę powiązaną, ale jako elementów użyć bloków pamięci (minitablic). Nie da się tego obsłużyć jako tablicy (brak ciągłości), ale możesz opakować to funkcjami-generatorami, które zadbają o przeliczanie indeksu i skakanie do właściwego węzła, lub możesz po zakończeniu wczytywania danych zaalokować przestrzeń na wszystko (jak sobie będziesz zapisywał ile tam wleciało, to będziesz wiedział ile zaalokować) i po kolei przekopiować dane do ciągłej tablicy (memcpy), ale to ostatnie rozwiązanie jest pamięciożerne (zajmujesz ponad dwa razy tyle pamięci). Pytanie tylko czy taki stopień skomplikowania ma sens. W przypadku Twojego programu prościej jest dodać sobie licznik do pętli i kiedy wpiszesz wartość numer 100 (w przypadku tablicy znaków) lub 99 (w przypadku c-stringa), przerywasz zczytywanie wejścia i kontynuujesz wykonanie programu. W ten sposób nie odwrócisz całego ciągu, który chciał podać user, ale nie wyjedziesz poza tablicę i jakiś tam wynik zwrócisz, a o to chyba chodzi tutaj.
W C nie ma czegoś takiego jak kontener samorozszerzający się. Jak zaalokujesz jakieś miejsce w pamięci (statycznie lub dynamicznie), to finito, tylko tyle będziesz miał i koniec. Są różne biblioteki np. do stringów, które rozwiązują ten problem na różne sposoby (np. kiedyś była taka biblioteka bstring, która miała rozszerzający się kontener na c-stringi), ale wtedy nie masz zbytnio wyboru co do metody zwiększania takiego ciągu. Nie ma niestety najlepszego rozwiązania. Albo zużywasz dużo pamięci, albo marnujesz cykle na przerzucanie danych między blokami. Wybór właściwego powinien zależeć od tego, co dla Ciebie ma większe znaczenie (w jakim środowisku będzie działał program, ile tych danych będzie, jak często będą napływać, w jakich porcjach itp. itd.)
Najprostszym rozwiązaniem jest alokacja odpowiednio dużej tablicy/bufora i pilnowanie żeby nie trafiło tam za dużo. Czasami maksymalną ilość danych da się przewidzieć. Weźmy taki przykład: powiedzmy, że budujesz ścieżkę do pliku stat
aktualnego procesu w systemie plików /proc (taki wynalazek na uniksach). Pełna ścieżka ma postać /proc/<identyfikator procesu>/stat
. Identyfikator procesu jest liczbą szesnastobitową bez znaku, czyli może mieć maksymalnie 5 cyfr (65535). Wiadomo zatem, że aby przechować całą ścieżkę potrzebujemy tablicy o długości 17 znaków (6 na /proc/
+ 5 na identyfikator procesu + 5 na /stat
+ 1 na bajt zerowy terminujący ciąg). Ponieważ PID pobierasz funkcją systemową, masz pewność że 17 znaków będzie wystarczyło, chyba że w przyszłości standard POSIX wprowadzi 32-bitowe identyfikatory procesów (marne szanse), ale to można załatwić makrem.
Jeśli pobierasz dane od użytkownika, nie masz pewności ile user wklepie na wejściu. A może się pomyli i na wejście przekieruje zawartość jakiegoś pliku, albo kot mu będzie łaził po klawiaturze, albo będzie złośliwy (user, nie kot)? Generalna zasada jest taka: nigdy nie ufamy danym z zewnątrz. Dotyczy to nie tylko długości, ale także np. formatu (jeśli ma to znaczenie). Oczekujesz, że user na wejście poda liczbę? A co jeśli wpisze dupa
? Zawsze powinieneś traktować użytkowników jak pawiany walące bez ładu młotkami w klawiaturę i sprawdzać czy dane wejściowe są zgodne z oczekiwaniami programu. Inaczej może się to źle skończyć. Wyjechanie poza tablicę może np. zmienić działanie, bo nadmiarowe dane nadpisały licznik używany w pętli. Program wtedy działa, ale nieprawidłowo i nie wiesz zbytnio dlaczego bez ślęczenia nad debuggerem.
Ja mam świadomość, że wiele osób uważa to za głupotę w programach na zajęcia. No bo po co się bawić w sprawdzanie zakresów, skoro do testów będą zawsze podawane prawidłowe dane, a wykładowcy zależy na rozwiązaniu problemu, a nie na obsłudze wszystkich możliwych błędów. Jest w tym trochę racji, ale z drugiej strony trzeba sobie wcześnie wyrabiać nawyki, żeby później (kiedy będzie to miało znaczenie np. dla bezpieczeństwa) zwracać na to uwagę "naturalnie".
EDIT: Autokorekta. Jest kontener samorozszerzający się. Jest nim plik, ale to zazwyczaj jest wolniejsze od realokacji i nie wszędzie możliwe do zrealizowania (środowisko bez pamięci stałej lub odpowiednika, chroot/jail bez praw do zapisu itp.)