Was bringt es, die Größe eines Arrays in einer Parameterdeklaration anzugeben?

Nov 19 2020
#include <stdio.h>

int a[] = {1,2};

void test(int in[3]){
  //
}
 
int main() {
 test(a); 
 return 0;
}

Im obigen Code int in[3]ist der gleiche wie int *in. Die Nummer 3macht eigentlich nichts und hat nicht einmal die richtige Größe, aber trotzdem beschwert sich der Compiler nicht. Gibt es einen Grund, warum diese Syntax in C akzeptiert wird, oder fehlt mir eine Funktionalität?

Antworten

5 EricPostpischil Nov 19 2020 at 19:40

Wenn eine Array-Parameterdeklaration eine konstante Größe enthält, kann sie nur als Dokumentation für Leser dienen, indem ihnen angegeben wird, welche Größe das Array von der Funktion erwartet. Für einen konstanten Ausdruck n, konvertiert der Compiler eine Array - Deklaration wie int in[n]zu int *in, wonach es keinen Unterschied zu den Compiler und so wird nichts durch den Wert beeinflusst n.

Ursprünglich in C wurden Funktionsparameter durch eine Liste von Deklarationen nach der anfänglichen Funktionsdeklaration angegeben, wie z.

int f(a, b, c)
int a;
float b;
int c[3];
{
    … function body
}

Ich vermute, dass Arraygrößen in diesen Deklarationen nur deshalb zulässig waren, weil sie dieselbe Grammatik wie andere Deklarationen verwendeten. Es wäre schwieriger gewesen, Compiler-Code und Dokumentation zu schreiben, die die Größen ausschlossen, als sie einfach zuzulassen, aber zu ignorieren. Als die Deklaration von Parametertypen innerhalb von Funktionsprototypen ( int f(int a, float b, int c[3])) eingeführt wurde, vermute ich, dass dieselbe Argumentation angewendet wurde.

Jedoch:

  • Wenn die Deklaration staticwie in enthält , int in[static n]muss das entsprechende Argument beim Aufruf der Funktion auf mindestens nElemente verweisen , gemäß C 2018 6.7.6.3 7. Compiler können dies zur Optimierung verwenden.
  • Wenn die Arraygröße keine Konstante ist, kann sie vom Compiler beim Aufruf der Funktion ausgewertet werden. Wenn die Funktionsdeklaration beispielsweise lautet void test(int in[printf("Hi")]), geben sowohl GCC 10.2 als auch Apple Clang 11.0 beim Aufrufen der Funktion „Hi“ aus. (Mir ist jedoch nicht klar, dass der C-Standard diese Bewertung erfordert.)
  • Diese Anpassung erfolgt nur für den tatsächlichen Array-Parameter, nicht für Arrays darin. Beispielsweise wird in der Parameterdeklaration int x[3][4]der Typ von xangepasst int (*)[4]. Die 4 bleibt ein Teil der Größe und hat Auswirkungen auf die Zeigerarithmetik mit x.
  • Wenn ein Parameter als Array deklariert wird, muss der Elementtyp vollständig sein. Im Gegensatz dazu muss ein als Zeiger deklarierter Parameter nicht auf einen vollständigen Typ verweisen. Gibt beispielsweise struct foo x[3]eine Diagnosemeldung aus, wenn struct foodiese nicht vollständig definiert wurde, dies jedoch struct foo *xnicht ist.
2 KrishnaKanthYenumula Nov 19 2020 at 12:45

Wenn wir die Größe des Arrays in der Funktionsdefinition angeben, kann es zum Überprüfen von Fehlern mit dem statischen Analysetool verwendet werden. Ich habe das cppcheckTool für den folgenden Code verwendet.

#include <stdio.h>
void test(int in[3])
{
  in[3] = 4;
}

Die Ausgabe ist:

Cppcheck 2.2
[test.cpp:4]: (error) Array 'in[3]' accessed at index 3, which is out of bounds.
Done!

Wenn Sie jedoch keine Größe angeben, erhalten Sie keinen Fehler von cppcheck.

#include <stdio.h>
void test(int in[])
{
   in[3] = 4;
}

Die Ausgabe ist:

Cppcheck 2.2
Done!

Im Allgemeinen muss die Größe des Arrays in der Funktionsdefinition nicht angegeben werden. Wir können die Größe des Arrays nicht in einer anderen Funktion mithilfe des sizeofOperators ermitteln, da nur der Wert des Zeigers kopiert wird. Daher ist die Eingabe des sizeofOperators vom Typ int*und nicht vom Typ int[](innerhalb der Funktion test()). Der Wert der Größe des Arrays wirkt sich also nicht auf den Code aus. Siehe den folgenden Code:

#include <stdio.h>

int a[] = {1, 2, 3, 4, 5, 6, 7, 8};

void test(int in[8]) // Same as void test(int *arr)
{
    unsigned int n = sizeof(in) / sizeof(in[0]); // sizeof(int*)/sizeof(int)
    printf("Array size inside test() is %d\n", n);
}

int main()
{
    unsigned int n = sizeof(a) / sizeof(a[0]);  //sizeof(int[])/sizeof(int)
    printf("Array size inside main() is %d\n", n);
    test(a);
    return 0;
}

Die Ausgabe ist:

Array size inside main() is 8
Array size inside test() is 2

Wir müssen also die Größe eines Arrays mit einer anderen Variablen übergeben.

1 Simson Nov 19 2020 at 11:56

In C gibt es keinen Unterschied zwischen einem Zeiger auf eine Struktur und einem Zeiger auf ein Array derselben Datenstruktur. Um die Startadresse des nächsten zu erhalten, erhöhen Sie einfach den Zeiger um die Größe der Daten. Da es unmöglich ist, die Größe nur anhand des Zeigers selbst zu bestimmen, müssen Sie diese als Programmierer angeben.

Versuchen wir, das Programm zu ändern

#include <stdio.h>

void test(int in[3]){
  printf("%d %d,%d,%d\n",in[0],in[1],in[2],in[3]); // !Sic bug intentional 
}
int main() {
 int a[] = {1,2};
 int b[] = {3,4};
 test(a); 
 test(b); 
 return 0;
}

Und führen Sie es aus:

$ gcc pointer_size.c  -o a.out  && ./a.out 

1 2,3,4
3 4,-1420617472,-1719256057

In diesem Fall werden die Arrays hintereinander platziert. Wenn Sie also bei Index 2 und 3 von a lesen, erhalten Sie die Daten von b, und wenn wir zu viel von b lesen, wird alles gelesen, was auf diesen Adressen vorhanden ist.

Dies ist bis heute eine sehr häufige Quelle für Sicherheitslücken.

1 Lundin Nov 19 2020 at 20:03

Für die C-Sprache und den Compiler spielt es keine Rolle, ob Sie die Größe angeben, da das Array ohnehin an einen Zeiger auf das erste Element angepasst wird.

Die Angabe der Größe kann jedoch die Fähigkeit zur statischen Analyse durch andere externe Tools als den Compiler verbessern. Ein statischer Analysator kann beispielsweise leicht erkennen, dass es sich um einen Fehler außerhalb des Bereichs handelt:

void test(int in[3]){
  in[3] = 0;
}

Aber es hat keine Ahnung, ob dies ein Fehler ist:

void test(int* in){
  in[3] = 0;
}

In diesem Zusammenhang kann die nicht vorhandene Typensicherheit zwischen verschiedenen Arraygrößen tatsächlich mithilfe eines Tricks gelöst werden - Arrays stattdessen per Zeiger übergeben. Weil ein Zeiger auf ein Array nicht verfällt und es wählerisch ist, die richtige Größe zu bekommen. Beispiel:

void test(int (*in)[3]){
  int* ptr = *in;
  ptr[3] = 0;
}

int foo[10];
test(&foo);  // compiler error

int bar[3];
test(&bar);  // ok

Dieser Trick erschwert jedoch das Lesen und Verstehen des Codes.