Timm Thaler hat geschrieben:Ich weiß ja nicht, was ihr für ein Pascal habt, aber bei mir ist ein Integer ohne Zuweisung immer 0 und ein Boolean immer False. Das ist selbst auf dem AVR so, da wird am Anfang erstmal der Ram genullt.
Na dann führ doch einfach mal dieses Program aus:
Code: Alles auswählen
program Test;
procedure foo();
var a: Integer;
begin
WriteLn(a);
a := 42;
end;
begin
foo();
foo();
end.
Der grund ist, Pascal garantiert keine wert für uninitialisierte Variablen. D.h. der wert der in der Variable steht ist der der an dieser memory location steht.
Wenn du das programm oben austestest sollte das folgende geschehen: Beim ersten aufruf von foo wird speicher auf dem stack für a alloziiert. Der stack ist sauber also steht da ne 0 drin. Nach dem die funktion foo verlassen wird wird der stack pointer wieder reduziert. Beim nächsten aufruf wird wieder neuer speicher alloziiert, der, da es sich um die selbe funktion mit selber signatur handelt, genau so belegt wird wie beim ersten aufruf. Das a des zweiten aufrufs liegt also der selben stelle wie das erste a. im ersten durchlauf wurde an diese stelle 42 geschrieben, daher ist die ausgabe dieses programms erst 0 und danach 42.
Getestet unter 64 bit linux 4.19 mit fpc 3.0.4. Da es sich hierbei um undefiniertes verhalten handelt, kann das bei einem unterschiedlichen system natürlich auch komplett unterschiedlichen code erzeugen, ist also eventuell nicht immer reproduzierbar.
pluto hat geschrieben:Ein Boolean ist False, ja, aber du kannst diesen Variable nicht nutzen, jedenfalls ist mir das mal aufgefallen, seit dem belege ich mein Variablen immer mit einem Passenden Wert oder auch Variablen Klassen, die ich mit NIL belege.
Damit ich sie abfragen kann. Versuch mal eine Variable Klasse abzufragen, die du nicht Installisiert hast oder mit einem Wert belegt hast.
Jedenfalls habe ich diese Erfahrung gemacht vor einiger ganzen Weile.
Beim boolean ist es ein besonderer fall, ein Boolean ist true solang im unterliegenden speicher eine zahl steht die nicht 0 ist. True als konstante (also wenn man schreib b:=true) ist der Numerische wert -1 oder 1 (glaube es war mal -1 oder ist -1 auf windows, auf meinem linux bekomm ich aber grade 1 raus). Beispiel
Code: Alles auswählen
program Test;
var b: boolean;
var p: ^Byte;
begin
p := PByte(@b);
p^ := 42;
if b = true then writeLn('true');
b := true;
writeln(p^);
end.
Ergebnis: True und 1.
Das problem mit Klassen das du ansprichst ist da etwas anders. Bei dem beispiel oben mit der funktion foo, wird die variable a damit initialisiert was zufällig an der speicherstelle vorher stand. Das ist soweit ja auch kein problem, bei klassen wirds aber lustig, denn klassen sind intern auch nur zeiger. Und wenn in einem Zeiger ein undefinierter wert drin steht ist das natürlich ein ganz anderes problem als wenn das in einem normalen objekt steht. Beim dereferenzieren (also zugreifen auf klassenelemente) wird also geschaut was steht an der adresse auf die der Zeiger zeigt. Da die undefiniert ist kann das alles sein. Und das ist besonders schlimm wenn es auf einen echten Speicherbereich zeigt, da man damit dann die daten aus einer ganz anderen Klasse bearbeiten kann, ohne es zu merken.
Beispiel:
Code: Alles auswählen
program test;
{$MODE ObjFPC}{$H+}
function Bar(): PInteger;
begin
new(Result);
Result^ := 24;
end;
procedure Foo;
var p: ^Integer;
begin
p^ := 42;
end;
var p: ^Integer;
begin
p:= bar;
foo;
WriteLn(p^);
end.
Zu guter letzt, wo wir grade beim Thema sind, man sollte auch immer brav freeAndNil statt free benutzen, sonst passiert sowas:
Code: Alles auswählen
program Test;
{$MODE OBJFPC}{$H+}
type
TTest = class
public
A: Integer;
B: Integer;
end;
var
t1, t2: TTest;
begin
t1 := TTest.Create;
t1.free;
t2 := TTEst.Create;
t1.A := 42;
t1.B := 15;
WriteLn(t2.A, ' ', t2.B);
end.
Zu dem Thema ist Pascal modern, von mir ein ganz klares nein. Pascal ist bewusst nicht modern. Was ich damit meine ist nicht unbedingt pascal ist nicht aktuell, aber es basiert völlig auf alten konzepten.
Nehmen wir statt rust o.ä. einfach mal C++ als beispiel. C++ und Object Pascal sind ähnlich alt, dennoch hat C++ mit C++11 eine gewaltige Modernisierung erfahren. Um mal zwei modernisierungen zu nennen wäre das 1. Einführen von Ownership konzepten, sodass die Speicherbereinigung impliziet durch destruktoren übernommen wird (std::unique_ptr, std::shared_ptr, etc.)
2. Einführen von Lambdas und Funktionen als Objekte (std::function, funktionsoperator, etc.)
Ob diese änderungen besser oder schlechter sind, darüber lässt sich streiten, aber, C++ Code von vor 2011 ist damit Konzeptionell komplett anders als Code nach 2011. Bei pascal wiederum ist die neuste größere änderung die implementation von Generics und Namespaces oder Typhelpern, änderungen die von der Struktur keinen großen Unterschied machen. Ein Delphi 7 Programm sieht immernoch fundamental gleich aus, benutzt die selben Paradigmen und den selben Code stil, wie ein aktuelles Lazarus projekt, und bis auf kleinere anpassungen kann man viele aktuelle Lazarus projekte (rein syntaktisch, nicht von den Komponenten/libs) auch wieder Delphi 7 fähig machen. Es gab keinen fundamentalen wechsel in Pascal, so wie es ihn in C++ gab.
C ist zum beispiel eine Sprache die zwar geupdated wird, aber, sowie pascal, eher wenig modernisiert wird. Grade in den letzen jahren kam man viel auf die Idee, jetzt wo leistung und speicher recht billig ist, bei Programmiersprachen mehr syntaktische mittel zu geben. z.B. Sprachen wie C# haben einen definierten wert für jeden typen wenn eine Variable nicht initialisiert wird. Im endeffekt heißt das, eine schreiboperation pro Variable mehr. In den 70ern, als Pascal entwickelt wurde, war das einfach verschwendete laufzeit, heute macht die halbe nanosekunde nichts aus.
Sprachen wie Rust, Go, C# oder andere versuchen aktiv aus dem bisher festgefahrenen Paradigmen auszusteigen, und neue Wege zu implementieren um Programmcode leserlicher, robuster und weniger Fehleranfällig zu machen, während alte Sprachen sich oft eher daran orientiert haben wie es letzendlich auf der Hardware laufen muss.
Und das ist in vieler hinsicht ziemlich gut. So sind in rust z.B. speicherlecks mehr oder weniger unmöglich zu erzeugen, und bei den vielen Nächten die ich schon wegen eines Speicherzugriffsfehler durchgemacht hab kann ich das nur gutheißen. Oder, um bei rust zu bleiben, bietet Rust durch seine Syntax dem Kompiler die möglichkeit diverse sachen formal zu verifizieren. Der kompiler kann also die teilkorrektheit deines Codes deutlich besser überprüfen als bei einer sprache die nicht so designt wurde, z.B. pascal. Daher bekommst du in rust viel bessere und mehr fehlermeldungen. Zu der zeit als Pascal entwickelt wurde wäre formale verifikation rein von der Performance zur Kompilezeit nicht möglich gewesen.
Aber ganz ehrlich, ich mag pascal grade weil es nicht so modern ist. Man ist komplett auf der Maschiene. Es gibt keine komplizierten Konstrukte, bei denen mir als entwickler völlig unklar ist wie der generierte Assembly code dann aussehen wird. Die ganzen spielereien von oben, das Pointer gemenge, etc. ist nur möglich weil das was ich in Pascal mache, direkt den generierten Maschinencode wiederspiegelt. Und als jemand der sich für den ganzen Low-Level kram interresiert ist das natürlich viel schöner als in einer Sprache die mir all diese sachen verbietet, bzw. bei der ich nicht verstehen kann was am ende passiert (und da die andere option dafür C wäre, ist Pascal mir doch um einiges lieber).
Um aber mal wieder zum Thema zurück zu kommen:
Wie man in Visual Studio eine DLL erstellt, kann ich nicht sagen. Man kann aber auch eine mit C kompilierte Datei direkt in Lazarus einbinden.
Code: Alles auswählen
{$LINK cfunc.o} // Diese sollte die function cfunc enthalten
function cfunc (a : PChar; i : longint) : longint; cdecl; external;
Ist wohl das einfachste.
Das konzept ist so. Mit C (und Unix und später Linux) wurde das ELF binary format (Executable Linkage format) eingeführt. Die idee ist grundsätzlich so. Eine kompilationsunit enthält einen bestimmten Programmcode. Diese Unit exportiert dabei funktionen, und importiert funktionen. Z.B. betrachten wir diesen C file:
Code: Alles auswählen
void WriteLn(const char *);
int main() {
WriteLn("Hallo Welt!");
return 0;
}
Dieses program exportiert die Funktion main, und importiert die Funktion WriteLn.
Wenn ich das ganze kompiliere zu einem Object File (gcc -c ./test.c), so erhalte ich diese unfertige kompilationsunit. Damit ich daraus jetzt ein Programm machen kann, muss ich die importierte Funktion WriteLn bereit stellen.
Nehme ich jetzt eine zweite C datei:
So exportiert diese die funktion WriteLn (und importiert printf, die lass ich jetzt aber einfach mal untern tisch fallen). Wenn ich jetzt also die beiden Object files habe (test.o und writeln.o), so kann ich die zusammen linken und somit die Funktion WriteLn in der test.c benutzen:
Code: Alles auswählen
$> gcc -c test.c
$> gcc -c writeln.c
$> gcc -o test test.o writeln.o
Das dabei resultierende Programm test verwendet die main funktion die in der test.c exportiert wird, und ruft darin die WriteLn auf die von writeln.c exportiert wird.
Der FPC unterstützt auch die nutzung von ELF object files (auch wenn pascal als sprache im gegensatz zu C nicht auf ELF angewiesen ist). Somit kannst du praktisch ein projekt zur hälfte in C schreiben, die andere hälfte in Pascal, und am ende einfach alles zusammen linken (Ist einfacher gesagt als gemacht).
Das gesagt, willst du wahscheinlich doch lieber auf statische Bibliotheken oder Dynamische gehen, da das rumgehampel mit Object files ein bisschen dumm ist (so hast du einen Object File pro C file, bei einer Static Library, was eine sammlung von Object files ist, kannst du mehrere C Files gleichzeitig exportieren).
Ganz nett dazu ist
dieses tutorial: LinkWenn du mit C und Pascal arbeitest solltest du dir aber mindestens mal
was zu Aufrufkonventionen durchlesen, und eventuell willst du dann auch die
cmem unit verwenden (ansonsten kannst du speicher der in C erzeugt wird in Pascal nicht freigeben und anders rum)