Poniższy kod kompiluje się bez problemów:
int main() {
printf("Hi" "Bye");
}
Jednak to się nie kompiluje:
int main() {
int test = 0;
printf("Hi" (test ? "Bye" : "Goodbye"));
}
Jaki jest tego powód?
c
string
syntax
concatenation
conditional-operator
José D.
źródło
źródło
"Hi"
i"Bye"
są to literały ciągów, a nie łańcuchy, jak są używane w standardowej bibliotece C. W przypadku literałów łańcuchowych kompilator dokona konkatenacji"H\0i" "B\0ye"
. Nie to samo zsprintf(buf,"%s%s", "H\0i" "B\0ye");
a (some_condition ? + : - ) b
printf("Hi" ("Bye"));
nie zadziała - nie wymaga operatora trójskładnikowego; nawias jest wystarczający (choćprintf("Hi" test ? "Bye" : "Goodbye")
również nie można go skompilować). Istnieje tylko ograniczona liczba tokenów, które mogą występować po literale ciągu. Przecinek,
, otwarty nawias kwadratowy[
, zamknięty nawias kwadratowy]
(jak w1["abc"]
- i tak, jest makabryczny), zamykający nawias okrągły)
, zamykający nawias klamrowy}
(w inicjatorze lub podobnym kontekście) i średnik;
są dozwolone (i inny literał ciągu); Nie jestem pewien, czy są inne.Odpowiedzi:
Zgodnie ze standardem C (5.1.1.2 Fazy tłumaczenia)
I dopiero potem
W tej konstrukcji
nie ma sąsiadujących tokenów literału ciągu. Więc ta konstrukcja jest nieprawidłowa.
źródło
(test ? "Bye" : "Goodbye")
ewaluować do żadnego z literałów łańcuchowych, które zasadniczo tworzą"Hi" "Bye"
lub"Hi Goodbye"
? (inne odpowiedzi na moje pytanie)Zgodnie ze standardem C11, rozdział §5.1.1.2, konkatenacja sąsiednich literałów ciągów:
dzieje się w fazie tłumaczenia . Z drugiej strony:
obejmuje operator warunkowy, który jest oceniany w czasie wykonywania . Tak więc w czasie kompilacji, podczas fazy tłumaczenia, nie ma żadnych sąsiednich literałów ciągów, dlatego konkatenacja nie jest możliwa. Składnia jest nieprawidłowa i dlatego została zgłoszona przez Twój kompilator.
Aby nieco rozwinąć część dlaczego , w fazie przetwarzania wstępnego sąsiednie literały łańcuchowe są łączone i reprezentowane jako pojedynczy literał ciągu (token). Magazyn jest odpowiednio przydzielany, a połączony literał ciągu jest traktowany jako pojedyncza jednostka (jeden literał ciągu).
Z drugiej strony, w przypadku konkatenacji w czasie wykonywania, miejsce docelowe powinno mieć wystarczającą ilość pamięci, aby pomieścić połączony literał ciągu, w przeciwnym razie nie będzie możliwości uzyskania dostępu do oczekiwanych połączonych danych wyjściowych. Teraz, w przypadku napisowych , są już przydzielone pamięci w czasie kompilacji i nie może być przedłużony , aby dopasować w każdym wejściem więcej przychodzących na lub dołączone do oryginalnej treści. Innymi słowy, nie będzie możliwości uzyskania dostępu do połączonego wyniku (zaprezentowania go) jako pojedynczego literału ciągu . Tak więc ta konstrukcja jest z natury niepoprawna.
Po prostu do Twojej wiadomości, do konkatenacji ciągów w czasie wykonywania ( nie literałów ) mamy funkcję biblioteczną,
strcat()
która łączy dwa ciągi . Uwaga, opis wspomina:Widzimy więc, że
s1
jest to ciąg znaków , a nie literał ciągu . Ponieważ jednak zawartośćs2
nie jest zmieniana w żaden sposób, może to być dosłowny ciąg znaków .źródło
strcat
: tablica docelowa musi być wystarczająco długa, aby otrzymać znaki zs2
plus terminator zerowy po znakach już tam obecnych.Preprocesor przeprowadza konkatenację literału ciągów znaków w czasie kompilacji. Nie ma sposobu, aby ta konkatenacja była świadoma wartości
test
, która nie jest znana, dopóki program faktycznie nie zostanie wykonany. Dlatego nie można łączyć tych literałów ciągów.Ponieważ ogólny przypadek jest taki, że nie miałbyś takiej konstrukcji dla wartości znanych w czasie kompilacji, standard C został zaprojektowany, aby ograniczyć funkcję auto-konkatenacji do najbardziej podstawowego przypadku: kiedy literały są dosłownie obok siebie .
Ale nawet gdyby nie sformułował tego ograniczenia w ten sposób lub gdyby ograniczenie zostało inaczej skonstruowane, twój przykład nadal byłby niemożliwy do zrealizowania bez przekształcenia konkatenacji w proces wykonawczy. Do tego mamy funkcje biblioteczne, takie jak
strcat
.źródło
Ponieważ C nie ma
string
typu. Literały ciągów są kompilowane dochar
tablic, do których odwołuje sięchar*
wskaźnik.C umożliwia łączenie sąsiednich literałów w czasie kompilacji , jak w pierwszym przykładzie. Sam kompilator C ma pewną wiedzę na temat łańcuchów. Ale ta informacja nie jest obecna w czasie wykonywania, a zatem nie może nastąpić konkatenacja.
Podczas procesu kompilacji Twój pierwszy przykład jest „tłumaczony” na:
Zwróć uwagę, jak dwa ciągi są łączone w pojedynczą tablicę statyczną przez kompilator, zanim program kiedykolwiek zostanie wykonany.
Jednak Twój drugi przykład jest „przetłumaczony” na coś takiego:
Powinno być jasne, dlaczego to się nie kompiluje. Operator trójskładnikowy
?
jest oceniany w czasie wykonywania, a nie w czasie kompilacji, kiedy „łańcuchy” nie istnieją już jako takie, ale tylko jako prostechar
tablice, do których odwołują sięchar*
wskaźniki. W przeciwieństwie do sąsiednich literałów łańcuchowych , sąsiednie wskaźniki znaków są po prostu błędem składniowym.źródło
static const char *char_ptr_1 = {'H', 'i', 'B', 'y', 'e', '\0'};
byćstatic const char *char_ptr_1 = "HiBye";
i podobnie w przypadku pozostałych wskazówek?static const char *char_ptr_1 = "HiBye";
kompilator tłumaczy wiersz nastatic const char *char_ptr_1 = {'H', 'i', 'B', 'y', 'e', '\0'};
, więc nie, nie powinien być zapisywany „jak napis”. Jak mówi Odpowiedź, łańcuchy są kompilowane do tablicy znaków, a jeśli przypisujesz tablicę znaków w jej najbardziej „surowej” formie, użyłbyś listy znaków oddzielonych przecinkami, tak jakstatic const char *char_ptr_1 = {'H', 'i', 'B', 'y', 'e', '\0'};
static const char str[] = {'t', 'e', 's', 't', '\0'};
to to samo costatic const char str[] = "test";
,static const char* ptr = "test";
to nie to samo costatic const char* ptr = {'t', 'e', 's', 't', '\0'};
. Pierwsza jest ważna i będzie się kompilować, ale druga jest nieprawidłowa i robi to, czego oczekujesz.Jeśli naprawdę chcesz, aby obie gałęzie tworzyły stałe łańcuchowe czasu kompilacji, które będą wybierane w czasie wykonywania, potrzebujesz makra.
źródło
Twój kod używający operatora trójargumentowego warunkowo wybiera między dwoma literałami ciągów. Bez względu na znany lub nieznany stan, nie można tego ocenić w czasie kompilacji, więc nie można go skompilować. Nawet to stwierdzenie
printf("Hi" (1 ? "Bye" : "Goodbye"));
się nie skompiluje. Powód został szczegółowo wyjaśniony w powyższych odpowiedziach. Inna możliwość uczynienia takiej instrukcji przy użyciu operatora trójskładnikowego ważnego do kompilacji , wymagałaby również zastosowania znacznika formatu i wyniku potrójnej instrukcji operatora sformatowanej jako dodatkowy argument doprintf
. Nawet wtedyprintf()
wydruk sprawiałby wrażenie „konkatenacji” tych ciągów tylko w czasie wykonywania i już w czasie wykonywania .źródło
printf
nie wymaga specyfikatora formatu; gdyby tylko konkatenacja została wykonana w czasie kompilacji (a tak nie jest), użycie printf przez OP byłoby poprawne.printf()
wymagałoby znacznika formatu, co jest absolutnie nieprawdą. Poprawione!W
printf("Hi" "Bye");
masz dwie kolejne tablice znaków, które kompilator może przekształcić w jedną tablicę.W
printf("Hi" (test ? "Bye" : "Goodbye"));
masz jedną tablicę, po której następuje wskaźnik do znaku (tablica przekonwertowana na wskaźnik do jej pierwszego elementu). Kompilator nie może scalić tablicy i wskaźnika.źródło
Aby odpowiedzieć na pytanie - przejdę do definicji printf. Funkcja printf oczekuje const char * jako argumentu. Dowolny literał łańcuchowy, taki jak „Hi”, jest stałym char *; jednakże wyrażenie takie jak
(test)? "str1" : "str2"
NIE jest const char *, ponieważ wynik takiego wyrażenia znajduje się tylko w czasie wykonywania, a zatem jest nieokreślony w czasie kompilacji, co właściwie powoduje, że kompilator narzeka. Z drugiej strony - działa to doskonaleprintf("hi %s", test? "yes":"no")
źródło
(test)? "str1" : "str2"
NIE jestconst char*
... Oczywiście, że tak! Nie jest to wyrażenie stałe, ale jego typ jestconst char *
. Byłoby doskonale pisaćprintf(test ? "hi " "yes" : "hi " "no")
. Problem OP nie ma nic wspólnegoprintf
,"Hi" (test ? "Bye" : "Goodbye")
jest błędem składniowym bez względu na kontekst wyrażenia.To się nie kompiluje, ponieważ lista parametrów funkcji printf to
i
nie pasuje do listy parametrów.
gcc próbuje to zrozumieć, wyobrażając sobie to
jest listą parametrów i narzeka, że „Hi” nie jest funkcją.
źródło
printf()
listy argumentów, ale to dlatego, że wyrażenie nie jest prawidłowe nigdzie - nie tylko naprintf()
liście argumentów. Innymi słowy, wybrałeś zbyt wyspecjalizowany powód problemu; ogólny problem polega na tym, że"Hi" (
nie jest to poprawne w C, nie mówiąc już o wywołaniu doprintf()
. Proponuję usunąć tę odpowiedź, zanim zostanie poddana głosowaniu w dół.