2013-01-21 4 views
5

Rozważmy pętli while w ANSI C, którego jedynym celem jest, aby opóźnić wykonanie:Czy kompilator ANSI C może usunąć pętlę opóźnienia?

unsigned long counter = DELAY_COUNT; 
while(counter--); 

Widziałem to kiedyś wiele do egzekwowania opóźnień w systemach wbudowanych, gdzie np. nie ma funkcji i timery lub przerwań są ograniczone.

Mój odczyt standardu ANSI C polega na tym, że może on zostać całkowicie usunięty przez zgodny kompilator. To ma żadnych skutków ubocznych opisanych w 5.1.2.3:

dostępu do lotnego obiekt, modyfikowania obiektu, modyfikując plik lub wywołanie funkcji, które ma każdy z tych operacji są wszystkie skutki uboczne, które są zmiany w stanie środowiska wykonawczego.

... i ta sekcja mówi:

rzeczywistej implementacji nie musi oceniać część wyrazu, jeżeli można wywnioskować, że jego wartość nie jest używany i że bez bólu, skutki uboczne są produkowane (w tym wszelkie spowodowane przez wywołanie funkcji lub uzyskanie dostępu do obiektu zmiennego).

Czy to oznacza, że ​​pętlę można zoptymalizować? Nawet jeśli counter było volatile?

Uwagi:

  1. że to nie jest to samo, co Are compilers allowed to eliminate infinite loops?, ponieważ odnosi się do nieskończonych pętli i pojawiają się pytania o to, kiedy program może rozwiązać w ogóle. W takim przypadku program z pewnością przekroczy punkt w jakimś momencie, optymalizacji lub nie.
  2. Wiem, co robi GCC (usuwa pętlę dla -O1 lub wyższej, chyba że counter jest volatile), ale chcę wiedzieć, co dyktuje standard.
+0

Dopóki zachowanie * obserable * nie zostanie zmienione, standard C nie zabroniłby optymalizacji tej pętli. Tak więc standard nie dyktuje niczego konkretnego. –

Odpowiedz

11

C Standardowa zgodność jest zgodna z regułą "as-if", dzięki której kompilator może wygenerować dowolny kod, który zachowuje się "tak, jakby" wykonywał rzeczywiste instrukcje na maszynie abstrakcyjnej. Ponieważ nie wykonywanie żadnych operacji ma takie samo obserwowalne zachowanie "jak gdyby", wykonałeś pętlę, całkowicie nie można wygenerować dla niego kodu.

Innymi słowy, czas potrzebny do obliczenia na prawdziwym komputerze nie jest częścią "obserwowalnego" zachowania programu, jest jedynie zjawiskiem szczególnej implementacji.

Sytuacja jest inna w przypadku zmiennych volatile, ponieważ dostęp do lotności liczy się jako efekt "obserwowalny".

+0

@KingsIndian: hehe, nie martw się, myślę, że różnorodność wśród odpowiedzi zawsze są dobre :-) –

9

Czy to oznacza, że ​​pętlę można zoptymalizować?

Tak.

Nawet jeśli licznik był niestabilny?

Nie. Odczytuje i zapisuje zmienną lotną, która ma obserwowalne zachowanie, więc musi wystąpić.

2

Jeśli licznik jest volatile, kompilator nie może prawnie zoptymalizować pętli opóźnienia. W przeciwnym razie może.

Pętle opóźniające takie jak te są złe, ponieważ czas ich wypalania zależy od tego, jak kompilator wygeneruje dla nich kod. Korzystając z różnych opcji optymalizacji, można osiągnąć różne opóźnienia, co jest prawie niemożliwe od pętli opóźnienia.

Z tego powodu takie pętle opóźniające powinny być implementowane w języku asemblera, gdzie programista w pełni steruje kodem.Zwykle dotyczy to systemów wbudowanych z prostymi procesorami.

+2

To, czy implementujesz to w języku asemblera, jest w pewnym sensie nieistotne. Pętle opóźniające, które polegają na pewnej liczbie iteracji, są flakey - "opóźnienie" zmienia się w zależności od wielu czynników, z których prawie wszystkie są poza kontrolą programisty. ** Nie używaj takich pętli opóźniających. ** –

+1

@NikBougalis Niekoniecznie prawda. Jeśli procesor jest względnie głupi i wykonuje instrukcje za każdym razem używając tej samej liczby zegarów (ponieważ jest głupi i nie ma buforowania) i jeśli częstotliwość zegara się nie zmienia, opóźnienia będą dość stabilne, chyba że przerwania są włączone, a ISR wykonywane są często i zajmują dużo czasu. Podczas inicjalizacji sprzętu przerwania są często wyłączane tymczasowo lub do momentu zakończenia inicjalizacji. Jeśli wszystkie te warunki są spełnione, nie ma problemu z pętlami opóźnień. –

+0

@NikBougalis Procesory RISC na systemach wbudowanych mają w większości przypadków policzalne opóźnienia, a pętle opóźnień mogą dość dobrze wiązać swój czas. –

1

Standard dyktuje zachowanie, które widzisz. Jeśli utworzysz drzewo zależności dla DELAY_COUNT, zobaczysz, że ma on właściwość Modyfikuj bez użycia, co oznacza, że ​​można go wyeliminować. Jest to w odniesieniu do przypadku nieulotnego. W przypadku zmienności, kompilator nie może użyć drzewa zależności, aby spróbować usunąć tę zmienną i jako takie opóźnienie pozostaje (ponieważ lotne oznacza, że ​​sprzęt może zmienić wartość odwzorowaną w pamięci LUB w niektórych przypadkach oznacza "naprawdę tego potrzebuję, nie wyrzucaj tego away ") W przypadku, gdy patrzysz na to, czy zmienna oznaczona etykietą volatile mówi kompilatorowi, nie odrzucaj tego, ponieważ jest tutaj z jakiegoś powodu.

Powiązane problemy