Suche…


Einführung

In diesem Abschnitt werden einige der häufigsten Fehler beschrieben, die ein C-Programmierer kennen sollte und die er vermeiden sollte. Weitere Informationen zu unerwarteten Problemen und deren Ursachen finden Sie unter Undefiniertes Verhalten

Mischen von vorzeichenbehafteten und vorzeichenlosen Ganzzahlen in arithmetischen Operationen

Es ist normalerweise keine gute Idee, signed und unsigned Ganzzahlen in arithmetischen Operationen zu mischen. Was wird zum Beispiel in folgendem Beispiel ausgegeben?

#include <stdio.h>

int main(void)
{ 
    unsigned int a = 1000;
    signed int b = -1;

    if (a > b) puts("a is more than b");
    else puts("a is less or equal than b"); 

    return 0;
}  

Da 1000 mehr als -1 ist, würde man erwarten, dass die Ausgabe a is more than b ist. Dies wird jedoch nicht der Fall sein.

Arithmetische Operationen zwischen verschiedenen Integraltypen werden innerhalb eines allgemeinen Typs ausgeführt, der durch die so genannten üblichen arithmetischen Konvertierungen definiert wird (siehe Sprachspezifikation, 6.3.1.8).

In diesem Fall ist der "common type" unsigned int , weil, wie in Üblichen arithmetischen Konvertierungen angegeben ,

714 Wenn der Operand mit einem vorzeichenlosen Integer-Typ einen Rang hat, der größer oder gleich dem Rang des Typs des anderen Operanden ist, wird der Operand mit dem vorzeichenbehafteten Integer-Typ in den Typ des Operanden mit dem vorzeichenlosen Integer-Typ umgewandelt.

Das bedeutet, dass int Operand b vor dem Vergleich in unsigned int konvertiert wird.

Wenn -1 in ein unsigned int konvertiert wird, ist das Ergebnis der maximal mögliche unsigned int Wert, der größer als 1000 ist, was bedeutet, dass a > b falsch ist.

Beim Vergleichen fälschlicherweise = anstelle von ==

Der Operator = wird für die Zuweisung verwendet.

Der Operator == wird zum Vergleich verwendet.

Man sollte vorsichtig sein, die beiden nicht zu mischen. Manchmal schreibt man irrtümlich

/* assign y to x */
if (x = y) {
     /* logic */
}

wenn was wirklich gesucht wurde, ist:

/* compare if x is equal to y */
if (x == y) {
    /* logic */
}

Erstere weist x einen Wert von y zu und prüft, ob dieser Wert nicht Null ist, anstatt einen Vergleich durchzuführen. Dies ist äquivalent zu:

if ((x = y) != 0) {
    /* logic */
}

Es gibt Zeiten, in denen das Testen des Ergebnisses einer Zuweisung beabsichtigt ist und häufig verwendet wird, weil dadurch vermieden werden muss, dass Code kopiert werden muss und das erste Mal speziell behandelt werden muss. Vergleichen Sie

while ((c = getopt_long(argc, argv, short_options, long_options, &option_index)) != -1) {
        switch (c) {
        ...
        }
}

gegen

c = getopt_long(argc, argv, short_options, long_options, &option_index);
while (c != -1) {
        switch (c) {
        ...
        }
        c = getopt_long(argc, argv, short_options, long_options, &option_index);
}

Moderne Compiler erkennen dieses Muster und warnen nicht, wenn sich die Zuweisung in Klammern befindet (siehe oben). Möglicherweise warnt sie jedoch vor anderen Verwendungen. Zum Beispiel:

if (x = y)         /* warning */

if ((x = y))       /* no warning */
if ((x = y) != 0)  /* no warning; explicit */

Einige Programmierer verwenden die Strategie, die Konstante links vom Operator zu platzieren (im Allgemeinen als Yoda-Bedingungen bezeichnet ). Da Konstanten R-Werte sind, führt der Stil dieser Bedingung dazu, dass der Compiler einen Fehler ausgibt, wenn der falsche Operator verwendet wurde.

if (5 = y) /* Error */

if (5 == y) /* No error */

Dies verringert jedoch die Lesbarkeit des Codes erheblich und wird nicht als notwendig erachtet, wenn der Programmierer die guten C-Codierpraktiken befolgt und beim Vergleich zweier Variablen nicht hilfreich ist. Darüber hinaus geben viele moderne Compiler möglicherweise Warnungen aus, wenn Code mit Yoda-Bedingungen geschrieben wird.

Unvorsichtige Verwendung von Semikolons

Seien Sie vorsichtig mit Semikolons. Folgendes Beispiel

if (x > a);
   a = x;

bedeutet eigentlich:

if (x > a) {}
a = x;

Das heißt, x wird auf jeden Fall a zugewiesen, was möglicherweise nicht das war, was Sie ursprünglich wollten.

Das Fehlen eines Semikolons verursacht manchmal ein unbemerktes Problem:

if (i < 0) 
    return
day = date[0];
hour = date[1];
minute = date[2];

Das Semikolon hinter return wird verfehlt, so dass day = date [0] zurückgegeben wird.

Eine Methode, um dieses und ähnliche Probleme zu vermeiden, besteht darin, bei mehrzeiligen Bedingungen und Schleifen immer Klammern zu verwenden. Zum Beispiel:

if (x > a) {
    a = x;
}

Vergessen, ein zusätzliches Byte für \ 0 zuzuweisen

strlen immer daran, 1 zu strlen zu addieren, wenn Sie eine Zeichenfolge in einen malloc ed Puffer strlen .

char *dest = malloc(strlen(src)); /* WRONG */
char *dest = malloc(strlen(src) + 1); /* RIGHT */

strcpy(dest, src);

Dies liegt daran, dass strlen das nachgestellte \0 in der Länge nicht enthält. Wenn Sie den WRONG (wie oben gezeigt) strcpy , würde Ihr Programm beim Aufruf von strcpy undefiniertes Verhalten aufrufen.

Dies gilt auch für Situationen, in denen Sie eine Zeichenfolge mit bekannter maximaler Länge von stdin oder einer anderen Quelle lesen. Zum Beispiel

#define MAX_INPUT_LEN 42

char buffer[MAX_INPUT_LEN]; /* WRONG */
char buffer[MAX_INPUT_LEN + 1]; /* RIGHT */

scanf("%42s", buffer);  /* Ensure that the buffer is not overflowed */

Vergessen, Speicher freizugeben (Speicherverluste)

Eine bewährte Programmiermethode besteht darin, Speicher freizugeben, der direkt durch eigenen Code zugewiesen wurde, oder implizit durch Aufrufen einer internen oder externen Funktion, z. B. einer Bibliotheks-API wie strdup() . Wenn Sie den Speicher nicht freigeben, kann dies zu einem Speicherverlust führen, der sich in einem erheblichen Teil des vergeudeten Speichers ansammelt, der für Ihr Programm (oder das System) nicht verfügbar ist. Dies kann zu Abstürzen oder undefiniertem Verhalten führen. Probleme treten häufiger auf, wenn das Leck wiederholt in einer Schleife oder rekursiven Funktion auftritt. Das Risiko eines Programmfehlers steigt, je länger ein Programmleck läuft. Manchmal treten Probleme sofort auf; Zu anderen Zeiten werden Probleme für Stunden oder sogar Jahre des Dauerbetriebs nicht gesehen. Ausfälle der Speichermüdung können je nach den Umständen katastrophal sein.

Die folgende Endlosschleife ist ein Beispiel für ein Leck, das schließlich den verfügbaren Speicherverlust erschöpft, indem getline() , eine Funktion, die implizit neuen Speicher getline() , ohne diesen Speicher getline() .

#include <stdlib.h>
#include <stdio.h>

int main(void)
{
    char *line = NULL;
    size_t size = 0;

    /* The loop below leaks memory as fast as it can */

    for(;;) { 
        getline(&line, &size, stdin); /* New memory implicitly allocated */

        /* <do whatever> */

        line = NULL;
    }

    return 0;
}

Im Gegensatz dazu wird im folgenden Code auch die Funktion getline() verwendet. Diesmal wird der zugewiesene Speicher jedoch ordnungsgemäß freigegeben, wodurch ein Leck vermieden wird.

#include <stdlib.h>
#include <stdio.h>

int main(void)
{
    char *line = NULL;
    size_t size = 0;

    for(;;) {
        if (getline(&line, &size, stdin) < 0) {
            free(line);
            line = NULL;

            /* Handle failure such as setting flag, breaking out of loop and/or exiting */
        }

        /* <do whatever> */

        free(line);
        line = NULL;

    }

    return 0;
}

Undichtes Gedächtnis hat nicht immer greifbare Konsequenzen und ist nicht notwendigerweise ein funktionelles Problem. Während "Best Practice" rigoros den Speicher an strategischen Punkten und Bedingungen freigibt, um den Speicherbedarf des Speichers zu verringern und das Risiko der Speicherplatzermüdung zu senken, kann es Ausnahmen geben. Wenn ein Programm beispielsweise in Dauer und Umfang begrenzt ist, kann das Risiko eines Fehlschlags der Zuordnung als zu gering angesehen werden, um sich darüber Sorgen zu machen. In diesem Fall kann die Umgehung einer expliziten Freigabe als akzeptabel betrachtet werden. Beispielsweise geben die meisten modernen Betriebssysteme automatisch den gesamten von einem Programm belegten Speicherplatz frei, wenn es beendet wird, sei es aufgrund eines Programmfehlers, eines Systemaufrufs exit() , der Prozessbeendigung oder des Endes von main() . Das explizite Freigeben von Speicher zum Zeitpunkt der bevorstehenden Programmbeendigung könnte tatsächlich redundant sein oder einen Performance-Nachteil mit sich bringen.

Die Zuordnung kann fehlschlagen, wenn nicht genügend Speicher verfügbar ist. Die Behandlung von Fehlern sollte auf den entsprechenden Ebenen des Aufrufstapels berücksichtigt werden. getline() oben gezeigte getline() ist ein interessanter Anwendungsfall, da es sich um eine Bibliotheksfunktion handelt, die nicht nur dem Anrufer getline() , sondern auch aus verschiedenen Gründen fehlschlagen kann, die alle berücksichtigt werden müssen. Daher ist es bei der Verwendung einer C-API unbedingt erforderlich, die Dokumentation (Manpage) zu lesen und den Fehlerbedingungen und der Speicherauslastung besondere Aufmerksamkeit zu widmen und zu wissen, welche Softwareschicht die Last trägt, die zurückgegebener Speicher freizugeben.

Eine andere übliche Vorgehensweise bei der Speicherbehandlung besteht darin, die Speicherzeiger gleich nach dem Freigeben des von diesen Zeigern referenzierten Speichers auf NULL zu setzen, sodass diese Zeiger jederzeit auf Gültigkeit getestet werden können (z. B. auf NULL / Nicht-NULL geprüft), weil auf den freigegebenen Speicher zugegriffen wird kann zu schwerwiegenden Problemen führen, beispielsweise zum Abrufen von Mülldaten (Lesevorgang) oder Datenverfälschung (Schreibvorgang) und / oder einem Programmabsturz. In den meisten modernen Betriebssystemen ist das Freigeben von Speicherplatz 0 ( NULL ) ein NOP (z. B. harmlos), wie vom C-Standard gefordert. Wenn Sie also einen Zeiger auf NULL setzen, besteht kein Risiko, dass der Speicher doppelt freigegeben wird, wenn der Zeiger wird an free() . Denken Sie daran, dass das doppelte Freigeben des Speichers zu sehr zeitaufwändigen, verwirrenden und schwer zu diagnostizierenden Fehlern führen kann.

Zu viel kopieren

char buf[8]; /* tiny buffer, easy to overflow */

printf("What is your name?\n");
scanf("%s", buf); /* WRONG */
scanf("%7s", buf); /* RIGHT */

Wenn der Benutzer eine Zeichenfolge eingibt, die länger als 7 Zeichen ist ( buf für den buf ), wird der Speicher hinter dem buf überschrieben. Dies führt zu undefiniertem Verhalten. Schädliche Hacker nutzen dies häufig aus, um die Absenderadresse zu überschreiben und in die Adresse des bösartigen Codes des Hackers zu ändern.

Vergessen, den Rückgabewert von realloc in einen temporären Wert zu kopieren

Wenn realloc fehlschlägt, wird NULL . Wenn Sie den Wert des ursprünglichen Puffers dem Rückgabewert von realloc zuweisen und NULL , geht der ursprüngliche Puffer (der alte Zeiger) verloren und führt zu einem Speicherverlust . Die Lösung besteht darin, in einen temporären Zeiger zu kopieren, und wenn dieser temporär nicht NULL ist, dann in den realen Puffer kopieren.

char *buf, *tmp;

buf = malloc(...);
...

/* WRONG */
if ((buf = realloc(buf, 16)) == NULL)
    perror("realloc");

/* RIGHT */
if ((tmp = realloc(buf, 16)) != NULL)
    buf = tmp;
else
    perror("realloc");

Vergleich von Fließkommazahlen

Gleitkommatypen ( float , double und long double ) können einige Zahlen nicht genau darstellen, da sie eine endliche Genauigkeit haben und die Werte in einem binären Format darstellen. Genauso wie wir Dezimalstellen in der Basis 10 für Brüche wie 1/3 wiederholen, gibt es Brüche, die nicht auch binär endlich dargestellt werden können (wie 1/3, aber noch wichtiger 1/10). Vergleichen Sie Gleitkommawerte nicht direkt. Verwenden Sie stattdessen ein Delta.

#include <float.h> // for DBL_EPSILON and FLT_EPSILON
#include <math.h>  // for fabs()

int main(void)
{
    double a = 0.1; // imprecise: (binary) 0.000110...

    // may be false or true
    if (a + a + a + a + a + a + a + a + a + a == 1.0) {
        printf("10 * 0.1 is indeed 1.0. This is not guaranteed in the general case.\n");
    }

    // Using a small delta value.
    if (fabs(a + a + a + a + a + a + a + a + a + a - 1.0) < 0.000001) {
        // C99 5.2.4.2.2p8 guarantees at least 10 decimal digits
        // of precision for the double type.
        printf("10 * 0.1 is almost 1.0.\n");
    }

    return 0;
}

Ein anderes Beispiel:

gcc -O3   -g   -I./inc   -std=c11   -Wall -Wextra -Werror -Wmissing-prototypes -Wstrict-prototypes  -Wold-style-definition       rd11.c -o rd11 -L./lib -lsoq 
#include <stdio.h>
#include <math.h>

static inline double rel_diff(double a, double b)
{
    return fabs(a - b) / fmax(fabs(a), fabs(b));
}

int main(void)
{
    double d1 = 3.14159265358979;
    double d2 = 355.0 / 113.0;

    double epsilon = 1.0;
    for (int i = 0; i < 10; i++)
    {
        if (rel_diff(d1, d2) < epsilon)
            printf("%d:%.10f <=> %.10f within tolerance %.10f (rel diff %.4E)\n",
                   i, d1, d2, epsilon, rel_diff(d1, d2));
        else
            printf("%d:%.10f <=> %.10f out of tolerance %.10f (rel diff %.4E)\n",
                   i, d1, d2, epsilon, rel_diff(d1, d2));
        epsilon /= 10.0;
    }
    return 0;
}

Ausgabe:

0:3.1415926536 <=> 3.1415929204 within tolerance 1.0000000000 (rel diff 8.4914E-08)
1:3.1415926536 <=> 3.1415929204 within tolerance 0.1000000000 (rel diff 8.4914E-08)
2:3.1415926536 <=> 3.1415929204 within tolerance 0.0100000000 (rel diff 8.4914E-08)
3:3.1415926536 <=> 3.1415929204 within tolerance 0.0010000000 (rel diff 8.4914E-08)
4:3.1415926536 <=> 3.1415929204 within tolerance 0.0001000000 (rel diff 8.4914E-08)
5:3.1415926536 <=> 3.1415929204 within tolerance 0.0000100000 (rel diff 8.4914E-08)
6:3.1415926536 <=> 3.1415929204 within tolerance 0.0000010000 (rel diff 8.4914E-08)
7:3.1415926536 <=> 3.1415929204 within tolerance 0.0000001000 (rel diff 8.4914E-08)
8:3.1415926536 <=> 3.1415929204 out of tolerance 0.0000000100 (rel diff 8.4914E-08)
9:3.1415926536 <=> 3.1415929204 out of tolerance 0.0000000010 (rel diff 8.4914E-08)

Zusätzliche Skalierung in der Zeigerarithmetik

Bei der Zeigerarithmetik wird die Ganzzahl, die dem Zeiger hinzugefügt oder davon abgezogen werden soll, nicht als Änderung der Adresse, sondern als Anzahl der zu verschiebenden Elemente interpretiert.

#include <stdio.h>

int main(void) {
    int array[] = {1, 2, 3, 4, 5};
    int *ptr = &array[0];
    int *ptr2 = ptr + sizeof(int) * 2; /* wrong */
    printf("%d %d\n", *ptr, *ptr2);
    return 0;
}

Dieser Code führt eine zusätzliche Skalierung bei der Berechnung des Zeigers durch, der ptr2 zugewiesen ptr2 . Wenn sizeof(int) 4 ist, was in modernen 32-Bit-Umgebungen typisch ist, steht der Ausdruck für "8 elements after array[0] " (außerhalb des Bereichs) und ruft ein undefiniertes Verhalten auf .

Damit ptr2 auf 2 Elemente nach array[0] , sollten Sie einfach 2 hinzufügen.

#include <stdio.h>

int main(void) {
    int array[] = {1, 2, 3, 4, 5};
    int *ptr = &array[0];
    int *ptr2 = ptr + 2;
    printf("%d %d\n", *ptr, *ptr2); /* "1 3" will be printed */
    return 0;
}

Die explizite Zeigerarithmetik mit additiven Operatoren kann verwirrend sein, sodass die Verwendung von Array-Subskriptionen möglicherweise besser ist.

#include <stdio.h>

int main(void) {
    int array[] = {1, 2, 3, 4, 5};
    int *ptr = &array[0];
    int *ptr2 = &ptr[2];
    printf("%d %d\n", *ptr, *ptr2); /* "1 3" will be printed */
    return 0;
}

E1[E2] ist identisch mit (*((E1)+(E2))) ( N1570 6.5.2.1, Absatz 2) und &(E1[E2]) entspricht ((E1)+(E2)) ( N1570 6.5.3.2, Fußnote 102).

Wenn die Zeigerarithmetik bevorzugt wird, kann das Umsetzen des Zeigers auf die Adresse eines anderen Datentyps eine Byte-Adressierung ermöglichen. Seien Sie jedoch vorsichtig: Endianness kann zu einem Problem werden, und das Umschalten auf andere Typen als "Zeiger auf Zeichen" führt zu strengen Aliasing-Problemen .

#include <stdio.h>

int main(void) {
    int array[3] = {1,2,3};  // 4 bytes * 3 allocated
    unsigned char *ptr = (unsigned char *) array;  // unsigned chars only take 1 byte
    /*
     * Now any pointer arithmetic on ptr will match
     * bytes in memory.  ptr can be treated like it
     * was declared as: unsigned char ptr[12];
     */

    return 0;
}

Makros sind einfache Zeichenfolgen

Makros sind einfache Zeichenfolgen. (Streng genommen arbeiten sie mit Vorverarbeitungsmarkern, nicht mit beliebigen Zeichenketten.)

#include <stdio.h>

#define SQUARE(x) x*x

int main(void) {
    printf("%d\n", SQUARE(1+2));
    return 0;
}

Sie können erwarten, dass dieser Code 9 ( 3*3 ) druckt, aber tatsächlich werden 5 gedruckt, da das Makro auf 1+2*1+2 .

Sie sollten die Argumente und den gesamten Makroausdruck in Klammern einschließen, um dieses Problem zu vermeiden.

#include <stdio.h>

#define SQUARE(x) ((x)*(x))

int main(void) {
    printf("%d\n", SQUARE(1+2));
    return 0;
}

Ein weiteres Problem besteht darin, dass die Argumente eines Makros nicht garantiert werden, einmalig ausgewertet zu werden. Sie werden möglicherweise überhaupt nicht oder mehrmals bewertet.

#include <stdio.h>

#define MIN(x, y) ((x) <= (y) ? (x) : (y))

int main(void) {
    int a = 0;
    printf("%d\n", MIN(a++, 10));
    printf("a = %d\n", a);
    return 0;
}

In diesem Code wird das Makro zu ((a++) <= (10) ? (a++) : (10)) . Da a++ ( 0 ) kleiner als 10 , wird a++ zweimal ausgewertet und der Wert von a wird davon abweichen, was von MIN .

Dies kann durch die Verwendung von Funktionen vermieden werden. Beachten Sie jedoch, dass die Typen durch die Funktionsdefinition festgelegt werden, während Makros bei Typen (zu) flexibel sein können.

#include <stdio.h>

int min(int x, int y) {
    return x <= y ? x : y;
}

int main(void) {
    int a = 0;
    printf("%d\n", min(a++, 10));
    printf("a = %d\n", a);
    return 0;
}

Das Problem der Doppelbewertung ist nun behoben, aber diese min Funktion kann beispielsweise keine double verarbeiten, ohne sie zu kürzen.

Es gibt zwei Arten von Makroanweisungen:

#define OBJECT_LIKE_MACRO     followed by a "replacement list" of preprocessor tokens
#define FUNCTION_LIKE_MACRO(with, arguments) followed by a replacement list

Diese beiden Arten von Makros unterscheiden sich durch das Zeichen, das dem Bezeichner nach #define folgt: Wenn es sich um ein lparen-Objekt handelt , handelt es sich um ein funktionsähnliches Makro. Andernfalls handelt es sich um ein objektartiges Makro. Wenn die Absicht ist es, eine funktionsähnliche Makros zu schreiben, muss es kein Leerraum sein zwischen dem Ende des Namen des Makros und ( . Überprüfen Sie dies für eine ausführliche Erklärung.

C99

In C99 oder später könnten Sie static inline int min(int x, int y) { … } .

C11

In C11 können Sie einen 'typgenerischen' Ausdruck für min .

#include <stdio.h>

#define min(x, y) _Generic((x), \
                        long double: min_ld, \
                        unsigned long long: min_ull, \
                        default: min_i \
                        )(x, y)

#define gen_min(suffix, type) \
    static inline type min_##suffix(type x, type y) { return (x < y) ? x : y; }

gen_min(ld, long double)
gen_min(ull, unsigned long long)
gen_min(i, int)

int main(void)
{
    unsigned long long ull1 = 50ULL;
    unsigned long long ull2 = 37ULL;
    printf("min(%llu, %llu) = %llu\n", ull1, ull2, min(ull1, ull2));
    long double ld1 = 3.141592653L;
    long double ld2 = 3.141592652L;
    printf("min(%.10Lf, %.10Lf) = %.10Lf\n", ld1, ld2, min(ld1, ld2));
    int i1 = 3141653;
    int i2 = 3141652;
    printf("min(%d, %d) = %d\n", i1, i2, min(i1, i2));
    return 0;
}

Der generische Ausdruck könnte um weitere Typen erweitert werden, z. B. double , float , long long , unsigned long , long , unsigned - und entsprechende Makroaufrufe in gen_min .

Undefinierte Referenzfehler beim Linken

Einer der häufigsten Fehler beim Kompilieren tritt während der Verbindungsphase auf. Der Fehler sieht ähnlich aus:

$ gcc undefined_reference.c 
/tmp/ccoXhwF0.o: In function `main':
undefined_reference.c:(.text+0x15): undefined reference to `foo'
collect2: error: ld returned 1 exit status
$

Schauen wir uns den Code an, der diesen Fehler generiert hat:

int foo(void);

int main(int argc, char **argv)
{
    int foo_val;
    foo_val = foo();
    return foo_val;
}

Wir sehen hier eine Deklaration von foo ( int foo(); ), aber keine Definition davon (tatsächliche Funktion). Also haben wir dem Compiler den Funktionsheader zur Verfügung gestellt, aber es wurde an keiner Stelle eine solche Funktion definiert, so dass die Kompilierungsphase durchläuft, der Linker jedoch mit einem Undefined reference wird.
Um diesen Fehler in unserem kleinen Programm zu beheben, müssen wir nur eine Definition für foo hinzufügen:

/* Declaration of foo */
int foo(void);

/* Definition of foo */
int foo(void)
{
    return 5;
}

int main(int argc, char **argv)
{
    int foo_val;
    foo_val = foo();
    return foo_val;
}

Nun wird dieser Code kompiliert. Eine alternative Situation tritt auf, wenn sich die Quelle für foo() in einer separaten Quelldatei foo.c (und es gibt einen Header foo.h , der foo() deklariert, der in foo.c und undefined_reference.c ). Das foo.c besteht dann darin, sowohl die Objektdatei aus foo.c als auch undefined_reference.c zu verknüpfen oder beide Quelldateien zu kompilieren:

$ gcc -c undefined_reference.c 
$ gcc -c foo.c
$ gcc -o working_program undefined_reference.o foo.o
$

Oder:

$ gcc -o working_program undefined_reference.c foo.c
$

In einem komplexeren Fall handelt es sich um Bibliotheken wie im Code:

#include <stdio.h>
#include <stdlib.h>
#include <math.h>

int main(int argc, char **argv)
{
    double first;
    double second;
    double power;

    if (argc != 3)
    {
        fprintf(stderr, "Usage: %s <denom> <nom>\n", argv[0]);
        return EXIT_FAILURE;
    }

    /* Translate user input to numbers, extra error checking
     * should be done here. */
    first = strtod(argv[1], NULL);
    second = strtod(argv[2], NULL);

    /* Use function pow() from libm - this will cause a linkage 
     * error unless this code is compiled against libm! */
    power = pow(first, second);

    printf("%f to the power of %f = %f\n", first, second, power);

    return EXIT_SUCCESS;
}

Der Code ist syntaktisch korrekt. Deklaration für pow() existiert von #include <math.h> . Wir versuchen also zu kompilieren und zu verknüpfen, erhalten jedoch einen Fehler wie #include <math.h> :

$ gcc no_library_in_link.c -o no_library_in_link
/tmp/ccduQQqA.o: In function `main':
no_library_in_link.c:(.text+0x8b): undefined reference to `pow'
collect2: error: ld returned 1 exit status
$

Dies geschieht, weil die Definition für pow() während der Verbindungsphase nicht gefunden wurde. Um dies zu beheben, müssen wir angeben, dass wir eine Verknüpfung mit der Mathematikbibliothek namens libm indem Sie das Flag -lm . (Beachten Sie, dass es Plattformen wie macOS gibt, auf denen -lm nicht benötigt wird, aber wenn Sie die undefinierte Referenz erhalten, wird die Bibliothek benötigt.)

Deshalb führen wir die Kompilierungsphase erneut aus, wobei wir die Bibliothek (nach den Quell- oder Objektdateien) angeben:

$ gcc no_library_in_link.c -lm -o library_in_link_cmd
$ ./library_in_link_cmd 2 4
2.000000 to the power of 4.000000 = 16.000000
$

Und es funktioniert!

Missverständnis des Array-Zerfalls

Ein häufiges Problem in Code, der mehrdimensionale Arrays, Arrays von Zeigern usw. verwendet, ist die Tatsache, dass Type** und Type[M][N] grundsätzlich verschiedene Typen sind:

#include <stdio.h>

void print_strings(char **strings, size_t n)
{
    size_t i;
    for (i = 0; i < n; i++)
        puts(strings[i]);
}

int main(void)
{
    char s[4][20] = {"Example 1", "Example 2", "Example 3", "Example 4"};
    print_strings(s, 4);
    return 0;
}

Beispiel Compiler-Ausgabe:

file1.c: In function 'main':
file1.c:13:23: error: passing argument 1 of 'print_strings' from incompatible pointer type [-Wincompatible-pointer-types]
         print_strings(strings, 4);
                       ^
file1.c:3:10: note: expected 'char **' but argument is of type 'char (*)[20]'
     void print_strings(char **strings, size_t n)

Der Fehler gibt an, dass das Array s in der main an die Funktion print_strings , die einen anderen print_strings erwartet als erwartet. Es enthält auch eine Notiz , die Art zum Ausdruck , die von erwartet wird print_strings und die Art, die er von geben wurde main .

Das Problem beruht auf einem sogenannten Array-Zerfall . Was passiert, wenn s mit seinem Typ char[4][20] (Array von 4 Arrays mit 20 Zeichen) an die Funktion übergeben wird, wenn sie zu einem Zeiger auf das erste Element wird, als hätten Sie &s[0] geschrieben der Typ char (*)[20] (Zeiger auf 1 Array mit 20 Zeichen). Dies tritt für jedes Array auf, einschließlich eines Arrays von Zeigern, eines Arrays von Arrays (3-D-Arrays) und eines Arrays von Zeigern auf ein Array. Die folgende Tabelle zeigt, was passiert, wenn ein Array zerfällt. Änderungen in der Typbeschreibung werden hervorgehoben, um zu zeigen, was passiert:

Vor dem Verfall Nach dem Verfall
char [20] Array von (20 Zeichen) char * Zeiger auf (1 Zeichen)
char [4][20] Array von (4 Arrays mit 20 Zeichen) char (*)[20] Zeiger auf (1 Array mit 20 Zeichen)
char *[4] Array von (4 Zeiger auf 1 Zeichen) char ** Zeiger auf (1 Zeiger auf 1 Zeichen)
char [3][4][20] Array von (3 Arrays von 4 Arrays mit 20 Zeichen) char (*)[4][20] Zeiger auf (1 Array mit 4 Arrays mit 20 Zeichen)
char (*[4])[20] Array von (4 Zeiger auf 1 Array von 20 Zeichen) char (**)[20] Zeiger auf (1 Zeiger auf 1 Array mit 20 Zeichen)

Wenn ein Array zu einem Zeiger zerfallen kann, kann gesagt werden, dass ein Zeiger als Array mit mindestens einem Element betrachtet werden kann. Eine Ausnahme bildet ein Nullzeiger, der auf nichts zeigt und somit kein Array ist.

Array-Zerfall tritt nur einmal auf. Wenn ein Array zu einem Zeiger zerfallen ist, ist es jetzt ein Zeiger, kein Array. Wenn Sie einen Zeiger auf ein Array haben, denken Sie daran, dass der Zeiger als Array mit mindestens einem Element betrachtet werden kann, sodass bereits ein Zerfall des Arrays aufgetreten ist.

Mit anderen Worten, ein Zeiger auf ein Array ( char (*)[20] ) wird niemals ein Zeiger auf einen Zeiger ( char ** ). Um die print_strings Funktion zu print_strings , lassen Sie sie einfach den richtigen Typ erhalten:

void print_strings(char (*strings)[20], size_t n)
/* OR */
void print_strings(char strings[][20], size_t n)

Ein Problem tritt auf, wenn Sie möchten, dass die Funktion print_strings für ein beliebiges Array von Zeichen generisch ist: Was ist, wenn 30 Zeichen statt 20 Zeichen sind? Oder 50? Die Antwort besteht darin, vor dem Array-Parameter einen weiteren Parameter hinzuzufügen:

#include <stdio.h>

/*
 * Note the rearranged parameters and the change in the parameter name
 * from the previous definitions:
 *      n (number of strings)
 *   => scount (string count)
 *
 * Of course, you could also use one of the following highly recommended forms
 * for the `strings` parameter instead:
 *
 *    char strings[scount][ccount]
 *    char strings[][ccount]
 */
void print_strings(size_t scount, size_t ccount, char (*strings)[ccount])
{
    size_t i;
    for (i = 0; i < scount; i++)
        puts(strings[i]);
}

int main(void)
{
    char s[4][20] = {"Example 1", "Example 2", "Example 3", "Example 4"};
    print_strings(4, 20, s);
    return 0;
}

Das Kompilieren erzeugt keine Fehler und führt zu der erwarteten Ausgabe:

Example 1
Example 2
Example 3
Example 4

Übergeben nicht benachbarter Arrays an Funktionen, die "echte" mehrdimensionale Arrays erwarten

Wenn mehrdimensionalen Arrays mit Zuweisung malloc , calloc und realloc , ist ein gemeinsames Muster der inneren Arrays mit mehreren Anrufen zuzuweisen (auch wenn der Anruf nur einmal vorkommt, kann es in einer Schleife sein kann):

/* Could also be `int **` with malloc used to allocate outer array. */
int *array[4];
int i;

/* Allocate 4 arrays of 16 ints. */
for (i = 0; i < 4; i++)
    array[i] = malloc(16 * sizeof(*array[i]));

Die Bytedifferenz zwischen dem letzten Element eines der inneren Arrays und dem ersten Element des nächsten inneren Arrays darf nicht 0 sein, wie dies bei einem "echten" multidimensionalen Array der Fall wäre (z. B. int array[4][16]; ) :

/* 0x40003c, 0x402000 */
printf("%p, %p\n", (void *)(array[0] + 15), (void *)array[1]);

Berücksichtigt man die Größe von int , ergibt sich ein Unterschied von 8128 Bytes (8132-4), was 2032 int dimensionierten Array-Elementen entspricht, und das ist das Problem: Ein "reales" multidimensionales Array hat keine Lücken zwischen den Elementen.

Wenn Sie ein dynamisch zugewiesenes Array mit einer Funktion verwenden müssen, die ein "reales" mehrdimensionales Array erwartet, sollten Sie ein Objekt vom Typ int * zuordnen und zur Durchführung von Berechnungen Arithmetik verwenden:

void func(int M, int N, int *array);
...

/* Equivalent to declaring `int array[M][N] = {{0}};` and assigning to array4_16[i][j]. */
int *array;
int M = 4, N = 16;
array = calloc(M, N * sizeof(*array));
array[i * N + j] = 1;
func(M, N, array);

Wenn N ein Makro oder ein Integer-Literal ist und keine Variable, kann der Code einfach die natürlichere 2D-Array-Notation verwenden, nachdem ein Zeiger einem Array zugewiesen wurde:

void func(int M, int N, int *array);
#define N 16
void func_N(int M, int (*array)[N]);
...

int M = 4;
int (*array)[N];
array = calloc(M, sizeof(*array));
array[i][j] = 1;

/* Cast to `int *` works here because `array` is a single block of M*N ints with no gaps,
   just like `int array2[M * N];` and `int array3[M][N];` would be. */
func(M, N, (int *)array);
func_N(M, array);
C99

Wenn N kein Makro oder Integer-Literal ist, zeigt das array auf ein array mit variabler Länge (VLA). Dies kann immer noch mit func werden, indem in int * und eine neue Funktion func_vla würde func_N ersetzen:

void func(int M, int N, int *array);
void func_vla(int M, int N, int array[M][N]);
...

int M = 4, N = 16;
int (*array)[N];
array = calloc(M, sizeof(*array));
array[i][j] = 1;
func(M, N, (int *)array);
func_vla(M, N, array);
C11

Hinweis : VLAs sind ab C11 optional. Wenn Ihre Implementierung C11 unterstützt und das Makro __STDC_NO_VLA__ auf 1 definiert, bleiben Sie bei den pre-C99-Methoden.

Verwenden Sie Zeichenkonstanten anstelle von String-Literalen und umgekehrt

In C sind Zeichenkonstanten und Zeichenkettenliterale verschieden.

Ein Zeichen, das von einfachen Anführungszeichen wie 'a' ist 'a' ist eine Zeichenkonstante . Eine Zeichenkonstante ist eine ganze Zahl, deren Wert der Zeichencode ist, der für das Zeichen steht. Die Interpretation von Zeichenkonstanten mit mehreren Zeichen wie 'abc' ist von der Implementierung definiert.

Null oder mehr Zeichen, die von Anführungszeichen wie "abc" sind, sind ein String-Literal . Ein String-Literal ist ein nicht veränderbares Array, dessen Elemente vom Typ char . Die Zeichenfolge in doppelten Anführungszeichen sowie das abschließende Nullzeichen sind die Inhalte. "abc" hat also 4 Elemente ( {'a', 'b', 'c', '\0'} ).

In diesem Beispiel wird eine Zeichenkonstante verwendet, bei der ein Zeichenfolgenliteral verwendet werden soll. Diese Zeichenkonstante wird auf implementierungsdefinierte Weise in einen Zeiger konvertiert. Es besteht nur eine geringe Möglichkeit, dass der konvertierte Zeiger gültig ist. In diesem Beispiel wird daher undefiniertes Verhalten aufgerufen.

#include <stdio.h>

int main(void) {
    const char *hello = 'hello, world'; /* bad */
    puts(hello);
    return 0;
}

In diesem Beispiel wird ein String-Literal verwendet, bei dem eine Zeichenkonstante verwendet werden soll. Der aus dem String-Literal konvertierte Zeiger wird in einer implementierungsdefinierten Weise in eine Ganzzahl und in einer implementierungsdefinierten Art in char konvertiert. (Wie eine Ganzzahl in einen vorzeichenbehafteten Typ konvertiert wird, der den umzuwandelnden Wert nicht darstellen kann, ist implementierungsdefiniert, und ob char signiert ist, ist ebenfalls implementierungsdefiniert.)

#include <stdio.h>

int main(void) {
    char c = "a"; /* bad */
    printf("%c\n", c);
    return 0;
}

In fast allen Fällen wird der Compiler über diese Verwechslungen klagen. Ist dies nicht der Fall, müssen Sie weitere Warnmeldungen für den Compiler verwenden oder es wird empfohlen, einen besseren Compiler zu verwenden.

Rückgabewerte von Bibliotheksfunktionen ignorieren

Fast jede Funktion in der C-Standardbibliothek gibt bei Erfolg etwas und bei Fehlern etwas anderes zurück. malloc gibt beispielsweise bei Erfolg einen Zeiger auf den von der Funktion zugewiesenen Speicherblock zurück und, falls die Funktion den angeforderten Speicherblock nicht zuordnen konnte, einen Nullzeiger. Sie sollten daher immer den Rückgabewert überprüfen, um das Debuggen zu erleichtern.

Das ist schlecht:

char* x = malloc(100000000000UL * sizeof *x);
/* more code */
scanf("%s", x); /* This might invoke undefined behaviour and if lucky causes a segmentation violation, unless your system has a lot of memory */

Das ist gut:

#include <stdlib.h>
#include <stdio.h>

int main(void)
{
    char* x = malloc(100000000000UL * sizeof *x);
    if (x == NULL) {
        perror("malloc() failed");
        exit(EXIT_FAILURE);
    }

    if (scanf("%s", x) != 1) {
        fprintf(stderr, "could not read string\n");
        free(x);
        exit(EXIT_FAILURE);
    }

    /* Do stuff with x. */

    /* Clean up. */
    free(x);

    return EXIT_SUCCESS;
}

Auf diese Weise wissen Sie sofort die Fehlerursache, ansonsten könnten Sie stundenlang an einem völlig falschen Ort nach einem Fehler suchen.

Bei einem typischen Aufruf von scanf () wird kein Zeilenvorschubzeichen verwendet

Wenn dieses Programm

#include <stdio.h>
#include <string.h>

int main(void) {
    int num = 0;
    char str[128], *lf;

    scanf("%d", &num);
    fgets(str, sizeof(str), stdin);

    if ((lf = strchr(str, '\n')) != NULL) *lf = '\0';
    printf("%d \"%s\"\n", num, str);
    return 0;
}

wird mit dieser Eingabe ausgeführt

42
life

Die Ausgabe wird 42 "" anstelle der erwarteten 42 "life" .

Dies liegt daran, dass nach dem Aufruf von scanf() kein Newline-Zeichen nach 42 und fgets() vor dem Lesen der life . Dann fgets() auf zu lesen, bevor es das life liest.

Um dieses Problem zu vermeiden, ist eine Methode, die nützlich ist, wenn die maximale Länge einer Zeile bekannt ist, beispielsweise bei der Lösung von Problemen im Online-Richtersystem, die scanf() Verwendung von scanf() und das Lesen aller Zeilen über fgets() . Sie können sscanf() , um die gelesenen Zeilen zu analysieren.

#include <stdio.h>
#include <string.h>

int main(void) {
    int num = 0;
    char line_buffer[128] = "", str[128], *lf;

    fgets(line_buffer, sizeof(line_buffer), stdin);
    sscanf(line_buffer, "%d", &num);
    fgets(str, sizeof(str), stdin);

    if ((lf = strchr(str, '\n')) != NULL) *lf = '\0';
    printf("%d \"%s\"\n", num, str);
    return 0;
}

Eine andere Möglichkeit besteht darin, zu lesen, bis Sie nach der Verwendung von scanf() und vor der Verwendung von fgets() ein Zeilenumbruchzeichen fgets() .

#include <stdio.h>
#include <string.h>

int main(void) {
    int num = 0;
    char str[128], *lf;
    int c;

    scanf("%d", &num);
    while ((c = getchar()) != '\n' && c != EOF);
    fgets(str, sizeof(str), stdin);

    if ((lf = strchr(str, '\n')) != NULL) *lf = '\0';
    printf("%d \"%s\"\n", num, str);
    return 0;
}

Hinzufügen eines Semikolons zu einer # Definition

Es ist leicht, sich im C-Präprozessor zu verwirren und als Teil von C selbst zu behandeln. Dies ist jedoch ein Fehler, da der Präprozessor nur ein Textsubstitutionsmechanismus ist. Zum Beispiel, wenn Sie schreiben

/* WRONG */
#define MAX 100;
int arr[MAX];

der Code erweitert sich zu

int arr[100;];

Das ist ein Syntaxfehler. Das Hilfsmittel besteht darin, das Semikolon aus der Zeile #define zu entfernen. Es ist fast immer ein Fehler, ein #define mit einem Semikolon zu beenden.

Mehrzeilige Kommentare können nicht geschachtelt werden

In C schachteln mehrzeilige Kommentare / * und * / nicht.

Wenn Sie einen Codeblock oder eine Funktion mit diesem Kommentar kommentieren:

/*
 * max(): Finds the largest integer in an array and returns it.
 * If the array length is less than 1, the result is undefined.
 * arr: The array of integers to search.
 * num: The number of integers in arr.
 */
int max(int arr[], int num)
{
    int max = arr[0];
    for (int i = 0; i < num; i++)
        if (arr[i] > max)
            max = arr[i];
    return max;
}

Sie werden es nicht leicht kommentieren können:

//Trying to comment out the block...
/*

/*
 * max(): Finds the largest integer in an array and returns it.
 * If the array length is less than 1, the result is undefined.
 * arr: The array of integers to search.
 * num: The number of integers in arr.
 */
int max(int arr[], int num)
{
    int max = arr[0];
    for (int i = 0; i < num; i++)
        if (arr[i] > max)
            max = arr[i];
    return max;
}

//Causes an error on the line below...
*/

Eine Lösung ist die Verwendung von Kommentaren im C99-Stil:

// max(): Finds the largest integer in an array and returns it.
// If the array length is less than 1, the result is undefined.
// arr: The array of integers to search.
// num: The number of integers in arr.
int max(int arr[], int num)
{
    int max = arr[0];
    for (int i = 0; i < num; i++)
        if (arr[i] > max)
            max = arr[i];
    return max;
}

Nun kann der gesamte Block einfach auskommentiert werden:

/*

// max(): Finds the largest integer in an array and returns it.
// If the array length is less than 1, the result is undefined.
// arr: The array of integers to search.
// num: The number of integers in arr.
int max(int arr[], int num)
{
    int max = arr[0];
    for (int i = 0; i < num; i++)
        if (arr[i] > max)
            max = arr[i];
    return max;
}

*/

Eine andere Lösung besteht darin, das Deaktivieren von Code mithilfe der Kommentarsyntax zu vermeiden, stattdessen die Direktive #ifdef oder #ifndef . Diese Richtlinien tun Nest, so dass Sie frei Ihren Code im Stil Kommentar Sie bevorzugen.

#define DISABLE_MAX /* Remove or comment this line to enable max() code block */

#ifdef DISABLE_MAX
/*
 * max(): Finds the largest integer in an array and returns it.
 * If the array length is less than 1, the result is undefined.
 * arr: The array of integers to search.
 * num: The number of integers in arr.
 */
int max(int arr[], int num)
{
    int max = arr[0];
    for (int i = 0; i < num; i++)
        if (arr[i] > max)
            max = arr[i];
    return max;
}
#endif

Einige Handbücher gehen so weit, zu empfehlen, dass Codeabschnitte niemals kommentiert werden dürfen. Wenn Code vorübergehend deaktiviert werden soll, kann auf eine #if 0 Direktive zurückgegriffen werden.

Siehe #if 0, um Codeabschnitte auszublenden .

Array-Grenzen überschreiten

Arrays sind nullbasiert, das heißt, der Index beginnt immer bei 0 und endet mit der Länge des Index-Arrays minus 1. Der folgende Code gibt also nicht das erste Element des Arrays aus und gibt für den endgültigen Wert, den er druckt, eine Mülltonne aus.

#include <stdio.h>

int main(void)
{
    int x = 0;
    int myArray[5] = {1, 2, 3, 4, 5}; //Declaring 5 elements

    for(x = 1; x <= 5; x++) //Looping from 1 till 5.
       printf("%d\t", myArray[x]);

    printf("\n");
    return 0;
}

Ausgabe: 2 3 4 5 GarbageValue

Im Folgenden wird der richtige Weg beschrieben, um die gewünschte Leistung zu erzielen:

#include <stdio.h>

int main(void)
{
    int x = 0;
    int myArray[5] = {1, 2, 3, 4, 5}; //Declaring 5 elements

    for(x = 0; x < 5; x++) //Looping from 0 till 4.
       printf("%d\t", myArray[x]);

    printf("\n");
    return 0;
}

Ausgabe: 1 2 3 4 5

Es ist wichtig, die Länge eines Arrays zu kennen, bevor Sie mit dem Array arbeiten. Andernfalls können Sie den Puffer beschädigen oder einen Segmentierungsfehler verursachen, indem Sie auf Speicherbereiche zugreifen, die außerhalb der Grenzen liegen.

Rekursive Funktion - Die Basisbedingung wird übersehen

Die Berechnung der Fakultät einer Zahl ist ein klassisches Beispiel für eine rekursive Funktion.

Fehlende Grundbedingung:

#include <stdio.h>

int factorial(int n)
{
       return n * factorial(n - 1);
}

int main()
{
    printf("Factorial %d = %d\n", 3, factorial(3));
    return 0;
}

Typischer Ausgang: Segmentation fault: 11

Das Problem bei dieser Funktion ist, dass sie endlos in einer Schleife abläuft und einen Segmentierungsfehler verursacht. Sie benötigt eine Basisbedingung, um die Rekursion zu stoppen.

Grundbedingung angegeben:

#include <stdio.h>

int factorial(int n)
{
    if (n == 1) // Base Condition, very crucial in designing the recursive functions.
    {
       return 1;
    }
    else
    {
       return n * factorial(n - 1);
    }
}

int main()
{
    printf("Factorial %d = %d\n", 3, factorial(3));
    return 0;
}

Beispielausgabe

Factorial 3 = 6

Diese Funktion endet, sobald die Bedingung n gleich 1 ist (vorausgesetzt, der Anfangswert von n ist klein genug - die obere Grenze ist 12 wenn int eine 32-Bit-Menge ist).

Regeln, die zu beachten sind:

  1. Initialisieren Sie den Algorithmus. Rekursive Programme benötigen häufig einen Startwert. Dies wird entweder durch Verwendung eines an die Funktion übergebenen Parameters oder durch Bereitstellung einer Gateway-Funktion erreicht, die nicht rekursiv ist, aber die Ausgangswerte für die rekursive Berechnung einrichtet.
  2. Prüfen Sie, ob die aktuell verarbeiteten Werte mit dem Basisfall übereinstimmen. Wenn ja, den Wert bearbeiten und zurückgeben.
  3. Definieren Sie die Antwort in Bezug auf kleinere oder einfachere Unterprobleme oder Unterprobleme.
  4. Führen Sie den Algorithmus für das Unterproblem aus.
  5. Kombinieren Sie die Ergebnisse in der Formulierung der Antwort.
  6. Gib die Ergebnisse zurück.

Quelle: Rekursive Funktion

Logischen Ausdruck gegen 'true' prüfen

Der ursprüngliche C-Standard hatte keinen intrinsischen booleschen Typ, so dass bool , true und false keine inhärente Bedeutung hatten und oft von Programmierern definiert wurden. Normalerweise würde " true als "1" definiert und " false als 0 definiert.

C99

C99 fügt den eingebauten Typ _Bool und den Header <stdbool.h> , die definiert , bool (Erweiterung auf _Bool ), false und true . Sie können auch bool neu definieren, true und false , aber es wird darauf bool , dass dies eine veraltete Funktion ist.

Noch wichtiger ist, dass logische Ausdrücke alles, was zu Null ausgewertet wird, als falsch und jede Nicht-Null-Bewertung als wahr betrachtet. Zum Beispiel:

/* Return 'true' if the most significant bit is set */
bool isUpperBitSet(uint8_t bitField)
{
    if ((bitField & 0x80) == true)  /* Comparison only succeeds if true is 0x80 and bitField has that bit set */
    {
        return true;
    }
    else
    {
        return false;
    }
}

In dem obigen Beispiel versucht die Funktion zu prüfen, ob das obere Bit gesetzt ist, und gibt true wenn dies der true ist. Durch die explizite Prüfung auf " true wird die if (bitfield & 0x80) jedoch nur dann erfolgreich ausgeführt, wenn (bitfield & 0x80) als " true ausgewertet wird. (bitfield & 0x80) ist in der Regel 1 und sehr selten 0x80 . Überprüfen Sie entweder explizit den Fall, den Sie erwarten:

/* Return 'true' if the most significant bit is set */
bool isUpperBitSet(uint8_t bitField)
{
    if ((bitField & 0x80) == 0x80) /* Explicitly test for the case we expect */
    {
        return true;
    }
    else
    {
        return false;
    }
}

Oder bewerten Sie einen Wert ungleich Null als wahr.

/* Return 'true' if the most significant bit is set */
bool isUpperBitSet(uint8_t bitField)
{
    /* If upper bit is set, result is 0x80 which the if will evaluate as true */
    if (bitField & 0x80)
    {
        return true;
    }
    else
    {
        return false;
    }
}

Fließkomma-Literale sind standardmäßig vom Typ double

Beim Initialisieren von Variablen des Typs float mit Literalwerten oder beim Vergleich mit Literalwerten ist Vorsicht geboten, da reguläre Fließkomma-Literale wie 0.1 vom Typ double . Dies kann zu Überraschungen führen:

#include <stdio.h>
int main() {
    float  n;
    n = 0.1;
    if (n > 0.1) printf("Wierd\n");
    return 0;
}
// Prints "Wierd" when n is float

Hier wird n initialisiert und auf einfache Genauigkeit gerundet, was den Wert 0,10000000149011612 ergibt. Dann wird n zurück in die doppelte Genauigkeit umgewandelt, um mit 0.1 Literal verglichen zu werden (was 0,1000000000000000001 entspricht), was zu einer Nichtübereinstimmung führt.

Neben Rundungsfehlern führt das Mischen von float Variablen mit double schlechten Leistung auf Plattformen, die keine Hardware-Unterstützung für doppelte Genauigkeit bieten.



Modified text is an extract of the original Stack Overflow Documentation
Lizenziert unter CC BY-SA 3.0
Nicht angeschlossen an Stack Overflow