Wybacz, jeśli to pytanie jest naiwne. Rozważ następujący program:
#include <stdio.h>
int main() {
int i = 1;
i = i + 2;
5;
i;
printf("i: %d\n", i);
}
W powyższym przykładzie, oświadczenia 5;
i i;
wydawać całkowicie zbędny, jednak kompiluje kod bez ostrzeżenia lub błędy domyślnie (jednakże gcc nie rzucać warning: statement with no effect [-Wunused-value]
ostrzeżenie, gdy biegł z -Wall
). Nie mają one wpływu na resztę programu, więc dlaczego są uważane za prawidłowe stwierdzenia? Czy kompilator po prostu je ignoruje? Czy są jakieś korzyści z dopuszczenia takich oświadczeń?
;
po nim. To komplikuje język dodać więcej reguł, kiedy wyrażenia nie może być sprawozdanieprintf()
? Instrukcja w5;
zasadzie mówi „rób cokolwiek5
robi (nic) i ignoruj wynik. Twoje zdanieprintf(...)
jest„ rób cokolwiekprintf(...)
robi i ignoruj wyniki (wartość zwracana odprintf()
) ”. C traktuje to samo. Pozwala to również na kod taki jak(void) i;
gdziei
jest parametr funkcji, którą przesyłasz, abyvoid
oznaczyć ją jako celowo nieużywanąprintf()
ma wpływ, nawet jeśli zignorujesz wartość, którą w końcu zwraca. Natomiast5;
nie ma żadnego efektu.Odpowiedzi:
Jedną z zalet dopuszczenia takich instrukcji jest kod utworzony przez makra lub inne programy, a nie napisany przez ludzi.
Jako przykład wyobraź sobie funkcję,
int do_stuff(void)
która ma zwrócić 0 w przypadku sukcesu lub -1 w przypadku niepowodzenia. Może być tak, że obsługa „rzeczy” jest opcjonalna, więc możesz mieć plik nagłówkowy, który to robiTeraz wyobraź sobie kod, który chce robić rzeczy, jeśli to możliwe, ale może tak naprawdę nie dbać o to, czy odniesie sukces, czy nie:
Gdy
STUFF_SUPPORTED
wynosi 0, preprocesor rozszerzy wywołaniefunc2
do instrukcji, która właśnie czytatak więc w kompilatorze pojawi się „zbędne” stwierdzenie, które wydaje się niepokoić. Co jeszcze można zrobić? Jeśli ty
#define do_stuff() // nothing
, to kodfunc1
się zepsuje. (I nadal będziesz mieć pustą instrukcję,func2
która po prostu czyta;
, co jest być może nawet bardziej zbędne). Z drugiej strony, jeśli faktycznie musisz zdefiniowaćdo_stuff()
funkcję, która zwraca -1, możesz ponieść koszty wywołania funkcji bez powodu.źródło
((void)0)
.assert
.Proste instrukcje w C są zakończone średnikiem.
Proste instrukcje w C są wyrażeniami. Wyrażenie jest kombinacją zmiennych, stałych i operatorów. Każde wyrażenie powoduje pewną wartość określonego typu, którą można przypisać do zmiennej.
Powiedziawszy, że niektóre „inteligentne kompilatory” mogą odrzucić 5; i ja; sprawozdania.
źródło
void
nie ma wartości.Stwierdzenia bezskuteczne są dozwolone, ponieważ trudniej byłoby je zakazać niż zezwolić. Było to bardziej istotne, gdy C został zaprojektowany po raz pierwszy, a kompilatory były mniejsze i prostsze.
Oświadczenie wyrażenie składa się z wypowiedzi po średnikiem. Jego zachowanie polega na ocenie wyrażenia i odrzuceniu wyniku (jeśli istnieje). Zwykle celem jest to, że ocena wyrażenia ma skutki uboczne, ale nie zawsze jest łatwe lub nawet możliwe ustalenie, czy dane wyrażenie ma skutki uboczne.
Na przykład wywołanie funkcji jest wyrażeniem, więc wywołanie funkcji poprzedzone średnikiem jest instrukcją. Czy to stwierdzenie ma jakieś skutki uboczne?
Nie można tego stwierdzić bez zobaczenia implementacji
some_function
.Co powiesz na to?
Prawdopodobnie nie - ale jeśli
obj
jest zdefiniowane jakovolatile
, to robi.Umożliwiając dowolny wyraz również osiągnąć w sposób ekspresyjny-oświadczeniu dodając średnik sprawia, że definicja język prostsze. Wymaganie, aby wyrażenie miało skutki uboczne, zwiększyłoby złożoność definicji języka i kompilatora. C opiera się na spójnym zestawie reguł (wywołania funkcji są wyrażeniami, przypisania są wyrażeniami, wyrażenie poprzedzone średnikiem jest instrukcją) i pozwala programistom robić to, co chcą, nie uniemożliwiając im robienia rzeczy, które mogą, ale nie muszą mieć sensu.
źródło
Wymienione instrukcje bez skutku są przykładami wyrażeń , których składnia jest podana w sekcji 6.8.3p1 standardu C w następujący sposób:
Cała sekcja 6.5 poświęcona jest definicji wyrażenia, ale luźno mówiąc, wyrażenie składa się ze stałych i identyfikatorów powiązanych z operatorami. W szczególności wyrażenie może zawierać operator przypisania i może nie zawierać wywołania funkcji.
Tak więc każde wyrażenie poprzedzone średnikiem kwalifikuje się jako wyrażenie wyrażenia. W rzeczywistości każdy z tych wierszy kodu jest przykładem wyrażenia wyrażenia:
Niektóre operatory zawierają efekty uboczne, takie jak zestaw operatorów przypisania i operatorów inkrementacji / dekrementacji przed / po, a operator wywołania funkcji
()
może mieć efekt uboczny w zależności od tego, co robi dana funkcja. Nie ma jednak wymogu, aby jeden z operatorów musiał wywoływać efekt uboczny.Oto inny przykład:
Jest to wywoływanie funkcji i odrzucanie wyniku, podobnie jak wywołanie
printf
w twoim przykładzie, ale w przeciwieństwieprintf
do samego wywołania funkcji nie ma efektu ubocznego.źródło
Czasami takie stwierdzenia są bardzo przydatne:
Lub gdy podręcznik referencyjny każe nam po prostu czytać rejestry, aby coś zarchiwizować - na przykład, aby wyczyścić lub ustawić flagę (bardzo powszechna sytuacja w świecie uC)
https://godbolt.org/z/6wjh_5
źródło
*SREG
jest lotny,*SREG;
nie ma żadnego wpływu na model określony przez standard C. Norma C określa, że ma zauważalny efekt uboczny.