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?
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?
"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 w 1["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)
1 Pierwszeństwo między regułami składni tłumaczenia określają następujące fazy 6)
- Sąsiednie tokeny literału ciągu są konkatenowane.
I dopiero potem
- Białe znaki oddzielające tokeny nie mają już znaczenia. Każdy token wstępnego przetwarzania jest konwertowany na token. Powstałe tokeny są analizowane składniowo i semantycznie i tłumaczone jako jednostka translacyjna .
W tej konstrukcji
"Hi" (test ? "Bye" : "Goodbye")
nie ma sąsiadujących tokenów literału ciągu. Więc ta konstrukcja jest nieprawidłowa.
(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:
Sąsiednie tokeny literału ciągu są konkatenowane.
dzieje się w fazie tłumaczenia . Z drugiej strony:
printf("Hi" (test ? "Bye" : "Goodbye"));
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:
char *strcat(char * restrict s1,const char * restrict s2);
strcat()Funkcja dołącza kopię napisu wskazywany przezs2(łącznie z kończącym znakiem null) na końcu łańcucha wskazywanego przezs1. Początkowy znaks2nadpisuje pusty znak na końcus1. […]
Widzimy więc, że s1jest to ciąg znaków , a nie literał ciągu . Ponieważ jednak zawartość s2nie jest zmieniana w żaden sposób, może to być dosłowny ciąg znaków .
strcat: tablica docelowa musi być wystarczająco długa, aby otrzymać znaki z s2plus 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.
Ponieważ C nie ma stringtypu. Literały ciągów są kompilowane do chartablic, 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:
int main() {
static const char char_ptr_1[] = {'H', 'i', 'B', 'y', 'e', '\0'};
printf(char_ptr_1);
}
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:
int main() {
static const char char_ptr_1[] = {'H', 'i', '\0'};
static const char char_ptr_2[] = {'B', 'y', 'e', '\0'};
static const char char_ptr_3[] = {'G', 'o', 'o', 'd', 'b', 'y', 'e', '\0'};
int test = 0;
printf(char_ptr_1 (test ? char_ptr_2 : char_ptr_3));
}
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 proste chartablice, 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.
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 na static 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 co static const char str[] = "test";, static const char* ptr = "test";to nie to samo co static 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.
#include <stdio.h>
#define ccat(s, t, a, b) ((t)?(s a):(s b))
int
main ( int argc, char **argv){
printf("%s\n", ccat("hello ", argc > 2 , "y'all", "you"));
return 0;
}
Jaki jest tego powód?
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 do printf. Nawet wtedy printf()wydruk sprawiałby wrażenie „konkatenacji” tych ciągów tylko w czasie wykonywania i już w czasie wykonywania .
#include <stdio.h>
int main() {
int test = 0;
printf("Hi %s\n", (test ? "Bye" : "Goodbye")); //specify format and print as result
}
printfnie 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.
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")
(test)? "str1" : "str2"NIE jest const char*... Oczywiście, że tak! Nie jest to wyrażenie stałe, ale jego typ jest const char * . Byłoby doskonale pisać printf(test ? "hi " "yes" : "hi " "no"). Problem OP nie ma nic wspólnego printf, "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
(const char *format, ...)
i
("Hi" (test ? "Bye" : "Goodbye"))
nie pasuje do listy parametrów.
gcc próbuje to zrozumieć, wyobrażając sobie to
(test ? "Bye" : "Goodbye")
jest listą parametrów i narzeka, że „Hi” nie jest funkcją.
printf()listy argumentów, ale to dlatego, że wyrażenie nie jest prawidłowe nigdzie - nie tylko na printf()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 do printf(). Proponuję usunąć tę odpowiedź, zanim zostanie poddana głosowaniu w dół.