Zusammenfassung: C-Sprache und Java

1. Der Unterschied zwischen Maschinencode und Bytecode

1. Einleitung

  • Maschinencode: Es handelt sich um einen binären Befehlscode, der über einen Assembler aus Assemblercode auf einer bestimmten Computerhardwareplattform kompiliert wird.
  • Bytecode: Es handelt sich um einen Zwischencode, bei dem es sich um einen binären Befehlscode handelt, der sich nicht auf eine bestimmte Computerplattform bezieht und über einen Java-Compiler aus Java-Quellcode kompiliert wird. Bytecode wird normalerweise auf der Java Virtual Machine ausgeführt.

2. Kompilierung von C und Java

Die C-Sprache ist eine statische und kompilierte Sprache. Maschinencode wird zur Kompilierungszeit generiert und direkt zur Laufzeit ausgeführt, sodass die Ausführungsgeschwindigkeit sehr hoch ist.

Java ist eine interpretierte Sprache, die den Quellcode in Bytecode übersetzen muss, bevor er von der virtuellen Maschine ausgeführt wird.

  • Wenn ein Java-Programm ausgeführt wird, muss die Java Virtual Machine Bytecode in Maschinencode umwandeln, um das Programm auszuführen. Dieser Prozess ist in zwei Schritte unterteilt: Zuerst wird der Hotcode mithilfe der Just-in-Time-Kompilierungstechnologie (JIT) sofort in lokalen Maschinencode kompiliert, und dann führt der lokale Maschinencode die Operation des Programms aus. Obwohl Bytecode keine direkt ausgeführte Maschinenanweisung ist, wird er daher schließlich in Maschinencode umgewandelt, um das Programm auszuführen.

2. Warum ist die Leistung der C-Sprache besser als die von Java?

1. Verbrauch von der Kompilierung bis zur Ausführung

Die C-Sprache generiert direkt den von der Maschine ausgeführten Maschinencode, während Java in einen Zwischencode wie Bytecode kompiliert werden muss und die JVM dann den Bytecode in Maschinencode kompilieren muss, um bei der Ausführung tatsächlich ausgeführt zu werden

2. Die C-Sprache benötigt keinen Garbage-Collection-Mechanismus

Auch der Java Garbage Collector selbst verbraucht viele Ressourcen, was zu einer Verringerung der Programmleistung führt.

Wenn beispielsweise ein Java-Programm ausgeführt wird, benötigt es zusätzliche Zeit und Platz, um nicht verwendete Objekte im Speicher zu überprüfen und zu bereinigen. Dieser Prozess muss den Speicher ständig scannen und feststellen, welche Objekte freigegeben werden können. Aufgrund der großen Menge an Objekten, die verarbeitet werden können, nimmt dieser Prozess viel Zeit und Platz in Anspruch.

In der C-Sprache, die Speicher manuell freigibt, können Programmierer die Zuweisung und Freigabe von Speicher direkt steuern und so den zusätzlichen Aufwand der Speicherbereinigung vermeiden, sodass das Programm schneller ausgeführt wird.

Darüber hinaus wird es in Java definitiv zu einer Speicherfragmentierung kommen, was ebenfalls zu einer geringen Speicherauslastung führt

3. Vorteile von Java und C

C-Sprache und Java haben hinsichtlich der Leistung unterschiedliche Vor- und Nachteile.

  • Die C-Sprache ist eine statische und kompilierte Sprache. Maschinencode wird zur Kompilierungszeit generiert und direkt zur Laufzeit ausgeführt, sodass die Ausführungsgeschwindigkeit sehr hoch ist. Die C-Sprache ermöglicht auch den direkten Zugriff auf Hardwareressourcen , was sie im Bereich der Systemprogrammierung und eingebetteten Entwicklung sehr beliebt macht.
    • Die C-Sprache verfügt nicht über einen automatischen Garbage-Collection-Mechanismus, sodass Programmierer den Speicher manuell verwalten müssen . Variablen und Daten in der Sprache C werden im Speicher gespeichert, und das Programm muss Speicher explizit zuweisen und freigeben, da sonst Probleme wie Speicherverluste auftreten.
      • Dies hat den Vorteil, dass die Leistung hoch ist, da kein Garbage Collector erforderlich ist
      • Der Nachteil besteht darin, dass die Anforderungen an Programmierer relativ hoch sind und sie immer auf Codes im Zusammenhang mit der Speicherwiederherstellung achten sollten
  • Im Gegensatz dazu ist Java eine interpretierte Sprache, die erfordert, dass der Quellcode in Bytecode übersetzt wird, bevor er von einer virtuellen Maschine ausgeführt wird.
    • Obwohl der JIT-Compiler der Java Virtual Machine den Code entsprechend unterschiedlichen Umgebungen und Daten optimieren kann, ist seine Ausführungsgeschwindigkeit langsamer als die der C-Sprache. Java verfügt außerdem über einen Garbage-Collection-Mechanismus, der sich in gewissem Maße auf die Laufgeschwindigkeit des Programms auswirkt, insbesondere wenn der Speicherverbrauch des Programms groß ist.
    • Allerdings hat Java auch seine Vorteile. Das objektorientierte Programmiermodell, die automatische Speicherverwaltung, die starke Typprüfung und andere Funktionen von Java sorgen für eine höhere Sicherheit, Wartbarkeit und Portabilität und ermöglichen eine effizientere Durchführung umfangreicher Softwareentwicklungen.

Deshalb:

  • Die C-Sprache bietet offensichtliche Leistungsvorteile und eignet sich für Szenarien, die eine hohe Geschwindigkeit erfordern und die Maschinenhardware direkt bedienen müssen.
  • Java hingegen eignet sich für die Softwareentwicklung im großen Maßstab, die eine höhere Sicherheit, Wartbarkeit und Portabilität erfordert.

4. C-Sprache direkter Zugriff auf Hardware-Ressourcengehäuse

Die C-Sprache kann direkt auf Hardwareressourcen zugreifen. Der Grund dafür ist, dass die C-Sprache eine statische, kompilierte Sprache ist, die Maschinencode zur Ausführung direkt generieren kann und die direkte Manipulation von Speicheradressen zur Steuerung der Hardware ermöglicht. Nachfolgend finden Sie ein Beispiel:

#include <stdio.h>
#include <conio.h>
#include <bios.h>

int main(void) {
    int ch;
    int status = _bios_keybrd(_KEYBRD_READ);
    
    printf("status = %04X\n", status);
    
    while ((ch = getch()) != ESC) {
        printf("key code: %02X\n", ch);
    }
    
    return 0;
}

Dieses Programm nutzt die Merkmale der C-Sprache, liest den Tastaturstatus mithilfe der Funktion _bios_keybrd in der Header-Datei bios.h und führt dann entsprechende Vorgänge entsprechend den Leseergebnissen aus . Dieser direkte Zugriff auf Hardwareressourcen ist bei der Systemprogrammierung und der Embedded-Entwicklung sehr nützlich.

Im Gegensatz dazu unterstützt Java keinen direkten Zugriff auf Hardwareressourcen. Dies liegt daran, dass eines der Hauptdesignziele der Java-Sprache darin besteht, die Sicherheit des Programms zu gewährleisten und Sicherheitsprobleme zu vermeiden, die durch den direkten Zugriff auf Hardwareressourcen verursacht werden. Um plattformübergreifende Kompatibilität zu erreichen, implementiert die Java Virtual Machine (JVM) eine Abstraktionsebene, um Systemressourcen einheitlich zu kapseln und zu verwalten und so Programmportabilität und -sicherheit sicherzustellen. Daher können Java-Programme Hardwareressourcen nicht direkt bedienen und müssen Hardwareressourcen indirekt über bestimmte APIs wie Java Native Interface (JNI) bedienen.

Obwohl Java Hardwareressourcen wie die C-Sprache nicht direkt bedienen kann, kann Java dennoch C-Sprachfunktionen über JNI aufrufen, um die Hardware-Betriebsanforderungen zu erfüllen. Es ist jedoch zu beachten, dass dies die Portabilität und Sicherheit von Java-Programmen beeinträchtigen kann und eine manuelle Speicherverwaltung und andere Probleme erfordert. Daher ist es notwendig, die Vor- und Nachteile abzuwägen und eine geeignete Methode zum Betrieb von Hardwareressourcen auszuwählen.

Im Folgenden finden Sie weiterhin einige Beispiele für den C-Zugriff auf Hardware:

/* 案例:访问摄像头设备 */
int main()
{
    int fd;
    fd = open("/dev/video0", O_RDWR);   //以读写方式打开摄像头设备
    ioctl(fd, VIDIOC_QUERYCAP, &cap);   //查询设备属性
    ...
    close(fd);   //关闭摄像头设备
    return 0;
}

这段代码通过调用 Linux 系统提供的摄像头操作接口(open、ioctl、close)实现对摄像头设备的访问,并查询设备属性。
/* 案例:访问麦克风 */
int main()
{
    int fd;
    fd = open("/dev/dsp", O_RDONLY);   //以只读方式打开麦克风设备
    ioctl(fd, SOUND_MIXER_READ_VOLUME, &vol);  //读取音量
    ...
    close(fd);   //关闭麦克风设备
    return 0;
}

这段代码通过调用 Linux 系统提供的麦克风操作接口(open、ioctl、close)实现对麦克风设备的访问,并读取音量。
/* 案例:访问打印机 */
int main()
{
    FILE *fp;
    fp = fopen("/dev/usb/lp0", "w");   //以写方式打开打印机设备
    fprintf(fp, "Hello, printer!\n");   //向打印机写入数据
    fclose(fp);   //关闭打印机设备
    return 0;
}

这段代码通过 Linux 文件系统提供的文件操作接口(fopen、fprintf、fclose)实现对打印机设备的访问,并向打印机写入数据。

/* 案例:访问键盘 */
int main()
{
    int fd;
    struct input_event event;
    fd = open("/dev/input/event0", O_RDONLY);   //以只读方式打开键盘设备
    while(1)
    {
        read(fd, &event, sizeof(event));   //读取键盘事件
        if (event.type == EV_KEY && event.value == 1)   //判断是否为按键事件
        {
            printf("Key pressed: %d\n", event.code);   //输出按键编号
        }
    }
    close(fd);   //关闭键盘设备
    return 0;
}

这段代码通过调用 Linux 系统提供的输入设备操作接口(open、read、close)实现对键盘设备的访问,并读取键盘事件,并判断是否为按键事件,最后输出按键编号。
/* 案例:访问鼠标 */
int main()
{
    int fd;
    struct input_event event;
    fd = open("/dev/input/mice", O_RDONLY);   //以只读方式打开鼠标设备
    while(1)
    {
        read(fd, &event, sizeof(event));   //读取鼠标事件
        if (event.type == EV_REL && event.code == REL_X)   //判断是否为横向移动事件
        {
            printf("X moved: %d\n", event.value);   //输出横向移动距离
        }
    }
    close(fd);   //关闭鼠标设备
    return 0;
}

过调用 Linux 系统提供的输入设备操作接口(open、read、close)实现对鼠标设备的访问,并读取鼠标事件,并判断是否为横向移动事件,最后输出横向移动距离。
/* 案例:访问定时器 */
int main()
{
    int fd;
    struct itimerval timer;
    fd = open("/dev/rtc0", O_RDONLY);   //以只读方式打开定时器设备
    timer.it_value.tv_sec = 1;   //设置定时时间为1秒
    timer.it_value.tv_usec = 0;
    timer.it_interval.tv_sec = 1;   //设置定时周期为1秒
    timer.it_interval.tv_usec = 0;
    setitimer(ITIMER_REAL, &timer, NULL);   //设置定时器
    while(1)
    {
        ...   //执行定时任务
    }
    close(fd);   //关闭定时器设备
    return 0;
}
这段代码通过调用 Linux 系统提供的定时器操作接口(open、setitimer、close)实现对定时器设备的访问,并设置定时时间和周期,最后执行定时任务。

5. .c-Dateien und .h-Dateien in C-Sprache

Die .c-Datei bezieht sich auf die Quellcodedatei der C-Sprache, die den Implementierungscode der C-Sprache enthält und den Kernteil des Programms darstellt. Es kann von einem Compiler in eine ausführbare Datei kompiliert und direkt auf dem Computer ausgeführt werden.

Die .h-Datei bezieht sich auf die Header-Datei (Header-Datei) , bei der es sich auch um eine Quellcodedatei in C-Sprache handelt. Es enthält Deklarationen von Funktionen, Variablen und Konstanten für die gemeinsame Nutzung von Implementierungen zwischen mehreren Quelldateien. Die Header-Datei wird normalerweise vor der .c-Datei eingefügt, und die darin definierten Funktionen und Variablen können in der .c-Datei verwendet werden.

Daher bilden .c-Dateien und .h-Dateien zusammen die Grundstruktur eines C-Sprachprogramms.

6. Makro in C-Sprache

Das Makro in der C-Sprache bezieht sich auf eine Vorverarbeitungsanweisung, die auch als Makrodefinition (Makrodefinition) bezeichnet wird. Es kann einen Satz Codetexte durch einen anderen Satz Codetexte ersetzen, um den Code zu vereinfachen und die Wiederverwendbarkeit des Codes zu verbessern. Makrodefinitionen werden normalerweise mit der #define-Direktive durchgeführt.

Makros sind Konstanten in Java sehr ähnlich und stellen im Wesentlichen Werte dar, die während der Programmausführung nicht geändert werden können .

Makros können jedoch verwendet werden, um Werte in verschiedenen Formen wie Konstanten, Ausdrücken, Funktionen usw. darzustellen. Konstanten in Java können nur Werte von Basistypen und Zeichenfolgentypen darstellen.

Makros sind nicht typgebunden, daher kann es zu Typfehlern kommen. Konstanten in Java unterliegen Typbeschränkungen, wodurch das Problem von Typfehlern vermieden wird.

Verwenden Sie Makrodefinitionen, um Konstanten zu definieren: #define PI 3.1415926
Auf diese Weise kann PI anstelle von 3.1415926 direkt im Code verwendet werden, was die Lesbarkeit und Wartbarkeit des Codes verbessert.

Obwohl die Makrodefinition den Code vereinfachen kann, ist zu beachten, dass es sich bei der Makrodefinition nur um eine einfache Textersetzung handelt, sodass leicht Fehler gemacht werden können, die nicht leicht zu finden sind, z. B. das Vergessen, den Parametern Klammern hinzuzufügen. Darüber hinaus können Makrodefinitionen die Lesbarkeit des Codes verringern und auch die Größe des Codes erhöhen. Daher ist bei der Verwendung von Makrodefinitionen sorgfältige Überlegung erforderlich.

Sieben, C-Präprozessor

Der C-Präprozessor ist nicht Teil des Compilers, sondern ein separater Schritt im Kompilierungsprozess. Kurz gesagt ist ein C-Präprozessor nichts anderes als ein Textersetzungstool , das den Compiler anweist, vor dem eigentlichen Kompilieren die erforderliche Vorverarbeitung durchzuführen. Wir werden C-Präprozessor als CPP abkürzen .

Alle Präprozessorbefehle beginnen mit einem Nummernzeichen (#) . Es muss das erste Nicht-Null-Zeichen sein und zur besseren Lesbarkeit sollten Präprozessoranweisungen mit der ersten Spalte beginnen. Nachfolgend sind alle wichtigen Präprozessoranweisungen aufgeführt:

Acht, C-Header-Datei

Eine Header-Datei ist eine Datei mit der Erweiterung  .h  , die C-Funktionsdeklarationen und Makrodefinitionen enthält und von mehreren Quelldateien gemeinsam genutzt wird. Es gibt zwei Arten von Header-Dateien: von Programmierern geschriebene Header-Dateien und Header-Dateien, die mit Compilern geliefert werden.

Um eine Header-Datei in einem Programm verwenden zu können,   muss sie mithilfe der C-Vorverarbeitungsanweisung #include referenziert werden.

 

Neun, C-Speicherverwaltung 

Die C-Sprache bietet mehrere Funktionen für die Speicherzuweisung und -verwaltung. Diese Funktionen finden Sie in  der Header-Datei <stdlib.h>  .

In der Sprache C wird der Speicher über Zeigervariablen verwaltet . Ein Zeiger ist eine Variable, die eine Speicheradresse speichert, die auf eine Variable eines beliebigen Datentyps verweisen kann, einschließlich Ganzzahlen, Gleitkommazahlen, Zeichen und Arrays. Die C-Sprache bietet einige Funktionen und Operatoren, mit denen Programmierer den Speicher bearbeiten können, einschließlich Zuweisen, Freigeben, Verschieben und Kopieren.

Fall:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
 
int main()
{
   char name[100];
   char *description;
 
   strcpy(name, "Zara Ali");
 
   /* 动态分配内存 */
   description = (char *)malloc( 30 * sizeof(char) );
   if( description == NULL )
   {
      fprintf(stderr, "Error - unable to allocate required memory\n");
   }
   else
   {
      strcpy( description, "Zara ali a DPS student.");
   }
   /* 假设您想要存储更大的描述信息 */
   description = (char *) realloc( description, 100 * sizeof(char) );
   if( description == NULL )
   {
      fprintf(stderr, "Error - unable to allocate required memory\n");
   }
   else
   {
      strcat( description, "She is in class 10th");
   }
   
   printf("Name = %s\n", name );
   printf("Description: %s\n", description );
 
   /* 使用 free() 函数释放内存 */
   free(description);
}

 Wenn der obige Code kompiliert und ausgeführt wird, erzeugt er das folgende Ergebnis:

Name = Zara Ali
Description: Zara ali a DPS student.She is in class 10th

Häufig verwendete Speicherverwaltungsfunktionen und -operatoren in C-Sprache

  • malloc()-Funktion: Wird zum dynamischen Zuweisen von Speicher verwendet. Es benötigt einen Parameter, die Größe des zuzuweisenden Speichers in Bytes, und gibt einen Zeiger auf den zugewiesenen Speicher zurück.

  • free()-Funktion: Wird verwendet, um zuvor zugewiesenen Speicher freizugeben. Als Argument wird ein Zeiger auf den Speicher verwendet, der freigegeben werden soll, und dieser Speicher wird als ungenutzt markiert.

  • calloc()-Funktion: Wird verwendet, um Speicher dynamisch zuzuweisen und auf Null zu initialisieren. Es benötigt zwei Parameter, die Anzahl der zuzuweisenden Speicherblöcke und die Größe jedes Speicherblocks in Bytes, und gibt einen Zeiger auf den zugewiesenen Speicher zurück.

  • realloc()-Funktion: Wird zur Neuzuweisung von Speicher verwendet. Es benötigt zwei Argumente, einen zuvor zugewiesenen Zeiger und eine neue Speichergröße, und versucht dann, die Größe des zuvor zugewiesenen Speicherblocks zu ändern. Es gibt einen Zeiger auf den neu zugewiesenen Speicher zurück, wenn die Anpassung erfolgreich war, andernfalls einen Nullzeiger.

  • sizeof-Operator: Wird verwendet, um die Größe (in Bytes) eines Datentyps oder einer Variablen abzurufen .

  • Zeigeroperator: Wird verwendet, um den Wert der Speicheradresse oder Variablen zu erhalten, auf die der Zeiger zeigt.

  • &-Operator: Wird verwendet, um die Speicheradresse einer Variablen abzurufen.

  • * Operator: Wird verwendet, um den Wert der Variablen zu erhalten, auf die der Zeiger zeigt.

  • -> Operator: Wird für den Zeigerzugriff auf Strukturmitglieder verwendet. Die Syntax lautet Zeiger->Mitglied, was (*Zeiger).Mitglied entspricht.

  • memcpy()-Funktion: Wird zum Kopieren von Daten vom Quellspeicherbereich in den Zielspeicherbereich verwendet. Es benötigt drei Parameter: einen Zeiger auf den Zielspeicherbereich, einen Zeiger auf den Quellspeicherbereich und die Größe der zu kopierenden Daten in Bytes.

  • memmove()-Funktion: Ähnlich der memcpy()-Funktion, kann jedoch überlappende Speicherbereiche verarbeiten. Es benötigt drei Parameter: einen Zeiger auf den Zielspeicherbereich, einen Zeiger auf den Quellspeicherbereich und die Größe der zu kopierenden Daten in Bytes.

Supongo que te gusta

Origin blog.csdn.net/w2009211777/article/details/130005302
Recomendado
Clasificación