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 znaks2
nadpisuje pusty znak na końcus1
. […]
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 .
strcat
: tablica docelowa musi być wystarczająco długa, aby otrzymać znaki z s2
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
.
Ponieważ C nie ma string
typu. Literały ciągów są kompilowane do char
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:
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 char
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.
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
}
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.
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ół.