2013-10-10 14 views
6

Często oprócz dostarczania deklaracji funkcji, standardowe nagłówki C mogą zapewniać "makro maskowania", dzięki czemu rzeczy stają się szybsze. Na przykład, jeśli zawierają ctype.h plik nagłówek uznaCzy istnieją jakieś ograniczenia standardu C, które umożliwiają implementację funkcji jako makr?

int isdigit(int c); 

ale może również maskować deklarację z makro. Wierzę, że to jest przenośnym isdigit makro zgodnie ze standardem C:

#define isdigit(c) ((c) >= '0' && (c) <= '9') 

Oczywiście, to makro jest również niebezpieczne, ponieważ wprowadza niezdefiniowanej zachowanie jeśli to zrobisz, gdy makro jest zdefiniowany:

int c = 'A'; 
printf("%d\n", isdigit(c++)); 

Aby uniknąć UB w tym hipotetycznym przypadku, muszę otoczyć nazwę funkcji przez parens: (isdigit)(c++). Moje pytanie brzmi: czy są jakieś ograniczenia dotyczące tego, jakie rodzaje makr maskowania może zdefiniować standardowy nagłówek? Czy mają gwarancję, że nie spowodują niezdefiniowanych zachowań, jeśli wyrażenie argumentu wywoła efekty uboczne, czy też technicznie mogą mieć dziwne zachowanie, takie jak widzieliśmy powyżej? Gdzie są granice?

+0

Zaproponowane makro nie jest zgodne. Z wyjątkami (opcjonalnych) implementacji makr funkcji 'getc()' i 'putc()', definicja makra funkcji standardowej może nie oceniać żadnego z jej argumentów więcej niż jeden raz. Jeśli zostanie wywołany jako 'isdigit (C++)' twoje makro zachowuje się nieprawidłowo, ale instrukcja działa poprawnie z zgodnymi makrami i funkcją. –

+0

Przepraszam, co rozumiesz przez "standardowe makra"? W każdym razie, kiedy powiedziałem "przenośny", miałem na myśli "dobrze zachowany na wszystkich zgodnych ze standardem kompilatorach C", kiedy przekazano odpowiedni argument bez efektów ubocznych. –

+0

Standard C stwierdza, że ​​dowolna standardowa funkcja może być również zaimplementowana jako makro. Ale wymaga również, aby dowolna implementacja makr dowolnej standardowej funkcji C (z wyjątkiem 'getc()' i 'putc()') musiała tylko raz ocenić jej argumenty, ponieważ makro musi zachowywać się dokładnie tak, jakby było funkcją. Ponadto, każda funkcja musi być zarówno zadeklarowana, jak i zaimplementowana jako funkcja, tak aby jej adres mógł być przyjmowany i przekazywany jako wskaźnik do funkcji. To właśnie informacje, które Paul Griffiths zacytował u ciebie ze standardu. Zauważ, że 'setjmp()' jest jawnie makrem. –

Odpowiedz

5

Per C11 7.1.4.1, „Korzystanie z funkcji bibliotecznych”, szczególnie ostatnia część cytowanych poniżej:

Każda funkcja zadeklarowana w nagłówku może być dodatkowo realizowane jako funkcja makro-jak zdefiniowano w nagłówek, więc jeśli funkcja biblioteczna jest zadeklarowana jawnie, gdy dołączony jest jej nagłówek, jedna z przedstawionych poniżej technik może być użyta do zapewnienia, że ​​deklaracja nie jest objęta takim makropoleceniem. ... Z tego samego powodu składniowego, to może przyjmować adres funkcji bibliotecznej, nawet jeśli jest to również zdefiniowane jako ma cro. Zastosowanie #undef w celu usunięcia dowolnej definicji makra zapewni również, że zostanie określona faktyczna funkcja. Każde wywołanie funkcji bibliotecznej zaimplementowanej jako makro powoduje rozwinięcie się do kodu, który dokładnie analizuje każdy z jego argumentów, w razie potrzeby, więc ogólnie jest bezpieczne używanie wyrażeń arbitralnych jako argumentów.

Należy zauważyć, że "ogólnie" na końcu jest ważne, tam i istnieją wyraźne wyjątki. C11 7.21.7.5.2 mówi "funkcja getc jest odpowiednikiem fgetc, z tym wyjątkiem, że jeśli jest zaimplementowana jako makro, może oszacować stream więcej niż raz, więc argument nigdy nie powinien być wyrażeniem z efektami ubocznymi", z podobnym językiem dla putc w C11 7.21.7.7.2. W tym konkretnym przypadku stream jest FILE *, więc w normalnych warunkach byłoby to trochę dziwne, gdyby było to wyrażenie z efektami ubocznymi, ale mogłoby się to zdarzyć. To samo dotyczy ich odpowiedników o szerokim charakterze: putwc() i getwc(). Nie jestem świadomy żadnych innych wyjątków takich jak ten.

+0

Interesujące - Zastanawiam się, czy wymagania getc() i putc() zostały faktycznie zmienione w C11 (lub C99 w tej sprawie) ... zobacz moją odpowiedź. –

+0

Myślę, że "ogólnie" jest ważne w ostatnim zdaniu. Na C11 7.21.7.5.2, "funkcja' getc' jest równoważna funkcji 'fgetc', z tym wyjątkiem, że jeśli jest zaimplementowana jako makro, może oceniać' stream' więcej niż jeden raz, więc argument nigdy nie powinien być wyrażeniem z efekty uboczne ", więc myślę, że wciąż jesteś poprawny. –

+2

Wyjątki 'getc()' i 'putc()' występowały w C89 (i C99), a także C11. Celem jest umożliwienie implementacji makr, która normalnie unika jakichkolwiek wywołań funkcji, ale takie makra mają trudności z kilkakrotnym unikaniem oceny argumentu strumienia plików. GCC zapewnia "wyrażenia ekspresji" jako sposób na to wszystko; są one jednak rozszerzeniem GCC, a nie standardową funkcją kompilatora C. –

1

stąd: http://www.qnx.com/developers/docs/6.5.0/index.jsp?topic=%2Fcom.qnx.doc.dinkum_en_ecpp%2Flib_over.html

Argumenty, które mają skutki uboczne oceniać w ten sam sposób, czy wyrażenie wykonuje makro ekspansję lub wywołuje funkcję. Makra dla funkcji getc i putc są jawnymi wyjątkami od tej reguły.

Znalazłem kilka innych odniesień mówiących o tej samej rzeczy (chociaż nic nie cytuje bezpośrednio normy C).

Wygląda więc na to, że tylko getc() i putc() może spowodować spustoszenie w sposób przewidziany. Oprócz tych dwóch, powinieneś być bezpieczny, zakładając, że twoja platforma jest co najmniej jedną rozsądną lub zgodną.