C Pointerei
pl
- programmiertechnik
sorry 4 Nerverei,
also ich hab da eine Funktion:
// Serialize Nameserver Query
int serNSQuery(char *query, char *qname, Sho type, Sho class){
char *tok = ".";
char *pch = NULL;
pch = strtok(qname, tok);
int i = 0;
for( ; pch != NULL; pch = strtok(NULL, tok)){
Oct len = strlen(pch);
query[i] = len;
memcpy(&query[i+1], pch, len);
i += 1 + len;
}
query[i] = 0; // Terminiern
query[i+1] = 0;
query[i+2] = 1; // type A
query[i+3] = 0;
query[i+4] = 1; // class IN
int qlen = i + 5; // Länge der Query
return qlen;
}
und damit ein Problem beim Aufruf. Setze ich
char query[512];
char qname[] = "example.org";
int qlen = serNSQuery(query, qname, 1, 1);
funktioniert alles, wenn ich jedoch die Funktion mit serNSQuery(query, "example.org", 1, 1)
aufrufe, schmiert die exe ab (es compliliert also). Was hab' ich nicht verstanden!?
Bitte mal um Hinweises, MfG
Moin,
also ich hab da eine Funktion:
// Serialize Nameserver Query int serNSQuery(char *query, char *qname, Sho type, Sho class){ char *tok = ".";
… und du hast ein konstantes Token, das nicht als const
deklariert ist:
const char tok[] = ".";
char *pch = NULL; pch = strtok(qname, tok);
… Und du hast da eine Funktion, die man mit Vorsicht verwenden sollte …
int i = 0;
… sowie einen Array-Index eines vorzeichenbehafteten Datentyps …
for( ; pch != NULL; pch = strtok(NULL, tok)){ Oct len = strlen(pch);
… eine Umdefinition des Datentyps size_t
oder einen möglichen Integer-Überlauf …
query[i] = len; memcpy(&query[i+1], pch, len); i += 1 + len; } query[i] = 0; // Terminiern query[i+1] = 0; query[i+2] = 1; // type A query[i+3] = 0; query[i+4] = 1; // class IN int qlen = i + 5; // Länge der Query return qlen; }
und damit ein Problem beim Aufruf.
Eins? Mehrere, siehe oben.
Setze ich
char query[512]; char qname[] = "example.org"; int qlen = serNSQuery(query, qname, 1, 1);
funktioniert alles, wenn ich jedoch die Funktion mit
serNSQuery(query, "example.org", 1, 1)
aufrufe, schmiert die exe ab (es compliliert also). Was hab' ich nicht verstanden!?
Viele Grüße
Robert
Ahh, ja, für strtok() darf der String nicht konstant sein 😉
Danke Dich!!
Hallo pl,
der entscheidende Satz aus der verlinkten Referenz:
If such character was found, it is replaced by the null character '\0' and the pointer to the following character is stored in a static location for subsequent invocations.
strtok modifiziert den übergebenen String. Man sollte also gut überlegen, ob man ihn im Urzustand noch braucht.
Und die Story mit der static Location finde ich auch gefährlich - in einem Multithreading-Umfeld könnte das auf die Nase fallen. Aus meiner Sicht eine Funktion aus dem Gruselkabinett der 60er.
Rolf
hi,
der entscheidende Satz aus der verlinkten Referenz:
If such character was found, it is replaced by the null character '\0' and the pointer to the following character is stored in a static location for subsequent invocations.
strtok modifiziert den übergebenen String. Man sollte also gut überlegen, ob man ihn im Urzustand noch braucht.
Genau. Das wusste ich schon vor 10 Jahren. Hatte ich nur vergessen.
Und die Story mit der static Location finde ich auch gefährlich - in einem Multithreading-Umfeld könnte das auf die Nase fallen. Aus meiner Sicht eine Funktion aus dem Gruselkabinett der 60er.
Mir gehts um die Serialisierung einer Anfrage an einen Nameserver
Hast Du eine Altenative zu strtok() ?
MfG
PS: Was mich nervt ist, daß C den Little Endian bevorzugt. Für Netzwerkanwendungen also immer eine Um-Rechnerei 😉
Hallo pl,
Hast Du eine Altenative zu strtok() ?
Hm. Ich muss erstmal verstehen was Du tust. Sieht so aus, als würdest Du einen String, der Punkte enthält, an den Punkten teilen. Und dann vor jedes Fragment die Länge setzen. Also
"Hallo.Welt.Der.Zahlen" wird zu "\x05Hallo\x04Welt\x03Der\x06Zahlen".
Richtig?
In dem Fall würde ich an Position strlen(qname)+1 beginnen und rückwärts laufen. Zeichen werden gezählt und wenn Du einen Punkt in qname findest, schreibst Du nicht den Punkt, sondern den Zähler und setzt den Zähler auf 0. Wenn Du durch bist, schreibst Du den letzten Zählerstand auf query[0].
Hab mal das hier ausprobiert:
#include <stdio.h>
#include <string.h>
int main()
{
char query[512] = "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz";
// 0....:....1....:...
char* qname = "forum.selfhtml.org";
// 0....:....1....:...
int p = strlen(qname)+1;
char z = 0;
for (int i=p-1; i>0; i--) {
char c = qname[i-1];
if (c == '.') {
c = z;
z = 0;
} else {
z++;
}
query[i] = c;
}
query[0] = z;
query[p] = 0;
query[p+1] = 0;
query[p+2] = 1;
query[p+3] = 0;
query[p+4] = 1;
printf("Result: p=%d, %s\nHex: ", p, query);
for (int i=0; i<=p+4; i++) {
printf("%02x ", query[i]);
}
return 0;
}
PS: Was mich nervt ist, daß C den Little Endian bevorzugt.
Tut es nicht. Dein Intel-Prozessor tut das. Nimm einen IBM Großrechner oder einen Motorola 68xxx Prozessor, die sind Big Endian.
Rolf
moin,
Hast Du eine Altenative zu strtok() ?
Hm. Ich muss erstmal verstehen was Du tust. Sieht so aus, als würdest Du einen String, der Punkte enthält, an den Punkten teilen. Und dann vor jedes Fragment die Länge setzen. Also
"Hallo.Welt.Der.Zahlen" wird zu "\x05Hallo\x04Welt\x03Der\x06Zahlen".
Richtig?
Korrekt. Nun, es gibt verschiedene Ansätze, in Perl sähe das übrigens so aus:
# split into labels
# Serialize Nameserver Request
sub packreq{
my $qname = shift; # example.org
my $type = shift || 1; # A
my $class = shift || 1; # IN
return pack("(C/a*)*", (split /\./, $qname), "").pack("nn", $type, $class);
}
Und die Länge der Binary läßt sich am Ergebnis feststellen. Auch wenn mittendrin Nullbytes sind.
PS: Was mich nervt ist, daß C den Little Endian bevorzugt.
Tut es nicht. Dein Intel-Prozessor tut das. Nimm einen IBM Großrechner oder einen Motorola 68xxx Prozessor, die sind Big Endian.
Stimmt natürlich. Aber am Perlcode siehst Du, wie einfach sich Perl damit tut, n
ist der Short in Networkorder. Damit und mit den anderen Schablonen wirst Du von der Architektur unabhängig: Perlcode läuft plattformübergreifend.
MfG
PS: Zu Deinem Code: Die Länge muss als Byte in ein Socket geschrieben werden.
Hallo pl,
PS: Was mich nervt ist, daß C den Little Endian bevorzugt.
Tut es nicht. Dein Intel-Prozessor tut das. Nimm einen IBM Großrechner oder einen Motorola 68xxx Prozessor, die sind Big Endian.
Stimmt natürlich. Aber am Perlcode siehst Du, wie einfach sich Perl damit tut,
n
ist der Short in Networkorder. Damit und mit den anderen Schablonen wirst Du von der Architektur unabhängig: Perlcode läuft plattformübergreifend.
Das kann C auch: Im Header arpa/inet.h
findest du ntohs
& Co, die für dich „das Denken übernehmen“: ntohs
bspw. steht für network to host short, das heißt, dass der short
-Wert (eigentlich uint16_t
) anschließend definitv in der richtigen Host-Endianess vorliegt – auf IBM-PC kompatiblen Prozessoren findet daher ein Byte-Swapping statt, bei PowerPC zum Beispiel nicht.
Viele Grüße
Robert
hi,
PS: Was mich nervt ist, daß C den Little Endian bevorzugt.
Tut es nicht. Dein Intel-Prozessor tut das. Nimm einen IBM Großrechner oder einen Motorola 68xxx Prozessor, die sind Big Endian.
Stimmt natürlich. Aber am Perlcode siehst Du, wie einfach sich Perl damit tut,
n
ist der Short in Networkorder. Damit und mit den anderen Schablonen wirst Du von der Architektur unabhängig: Perlcode läuft plattformübergreifend.Das kann C auch: Im Header
arpa/inet.h
findest duntohs
& Co, die für dich „das Denken übernehmen“:
Ne, da muss ich schon selbst aktiv werden, das Denken nimmt mir da htons() gar nicht ab. Also den Code so schreiben, daß er entweder auf einer LE oder BE Architektur funktioniert. Oder mit Bitoperationen arbeiten so wie ich das hier gemacht habe. Also händisch in die Byteorder eingreifen.
MfG
Moin,
Das kann C auch: Im Header
arpa/inet.h
findest duntohs
& Co, die für dich „das Denken übernehmen“:Ne, da muss ich schon selbst aktiv werden, das Denken nimmt mir da htons() gar nicht ab.
Doch.
Also den Code so schreiben, daß er entweder auf einer LE oder BE Architektur funktioniert.
Nein.
Oder mit Bitoperationen arbeiten so wie ich das hier gemacht habe. Also händisch in die Byteorder eingreifen.
Nein.
Beispiel ntohs
und htons
:
ntohs
stellt sicher, dass der in Network Byte Order vorliegende uint16_t
-Wert anschließend in der für deinen aktuellen Host korrekten Byte Order vorliegt. Auf einem Big Endian-System muss dazu nichts weiter getan werden, der Wert wird durchgereicht, auf einem Little Endian-System findet ein Byte-Swapping statt. Ob du auf einem BE oder LE bist, braucht dich hier gar nicht zu kümmern, ntohs
kümmert sich darum.htons
arbeitet genau anders herum: Von deiner Host Byte Order wird in die Network Byte Order übersetzt (LE → BE) oder der Wert einfach durchgereicht (BE → BE).Viele Grüße
Robert
Wir reden aneinander vorbei.
denn:
ntohs stellt sicher, dass der in Network Byte Order vorliegende uint16_t-Wert anschließend in der für deinen aktuellen Host korrekten Byte Order vorliegt.
Darum gehts doch gar nicht. Eine Netzwerkanwendung benötigt Zahlen in Networkorder (BE) und ob diese Endianness für den Host auf dem die Anwendung läuft zutrifft, ist völlig Wurst.
Sicher kann man einen int mit htons() oder htonl() in die Networkorder bringen aber auch nur wenn man sichergehen kann daß dieser int als LE vorliegt.
Deswegen schreibe ich ja auch auf meiner Seite wie man die Entwicklung von Netzwerk-Anwendungen mit Wireshark begleiten sollte, da werden Fehler dieser Art sofort sichtbar.
MfG
Moin,
Wir reden aneinander vorbei.
vollkommen korrekt, das zeigt deine Antwort:
ntohs stellt sicher, dass der in Network Byte Order vorliegende uint16_t-Wert anschließend in der für deinen aktuellen Host korrekten Byte Order vorliegt.
Darum gehts doch gar nicht. Eine Netzwerkanwendung benötigt Zahlen in Networkorder (BE) und ob diese Endianness für den Host auf dem die Anwendung läuft zutrifft, ist völlig Wurst.
Soweit korrekt.
Sicher kann man einen int mit htons() oder htonl() in die Networkorder bringen aber auch nur wenn man sichergehen kann daß dieser int als LE vorliegt.
Nein! Lies bitte noch einmal meinen Post und den Link auf die Manpage. ntohs
stellt sicher, dass immer in die korrekte Host Byte Order konvertiert wird und htons
immer korrekt in die Netzwerk Byte Order. Immer!
Viele Grüße
Robert
Ok, ich formuliere das Poblem mal anders:
Was nützt ein Datentyp dessen Endiannes systemabhängig ist? Gar nichts!
Genausowenig nützt dann solch ein struct:
typedef struct{
uint16_t id;
uint16_t flags;
usw...
}DNSHeader;
weil die Byteorder der Members systemabhängig sind.
Nein! Lies bitte noch einmal meinen Post und den Link auf die Manpage. ntohs stellt sicher, dass immer in die korrekte Host Byte Order konvertiert wird und htons immer korrekt in die Netzwerk Byte Order. Immer!
So!?
printf("%#x", htons(53) ); // 0x3500
Rauskommen muss aber 0x0035
! Wenn das Ergebnis richtig sein soll, muss der Entwickler wissen daß Port 53 als short LE die Zahl 13568 verkörpert.
MfG
Moin,
Was nützt ein Datentyp dessen Endiannes systemabhängig ist? Gar nichts!
Also sind Integer- und Double-Werte unnütz.
Nein! Lies bitte noch einmal meinen Post und den Link auf die Manpage. ntohs stellt sicher, dass immer in die korrekte Host Byte Order konvertiert wird und htons immer korrekt in die Netzwerk Byte Order. Immer!
So!?
printf("%#x", htons(53) ); // 0x3500
53
ist BE 0x0035
und LE 0x3500
und das Format-Flag %x
arbeitet mit der Endianess deines lokalen Systems. Es wäre doch auch sehr merkwürdig, wenn
printf("%#x\n", 53);
0x3500
ausgäbe anstelle von 0x35
. Wenn du überprüfen willst, ob htons
richtig funktioniert, musst du dir das Bitmuster anschauen, z.B.
uint16_t ui = 53;
fwrite(&ui, sizeof(ui), 1, file…);
/* … und die Ausgabedatei mit einem Hexeditor betrachten … */
/* … oder mit einem anderen Blick darauf schauen: */
typedef union {
uint16_t i;
char b[2];
} Bytes;
Bytes b;
b.i = ui;
printf("0x%x%x", b[0], b[1]);
Rauskommen muss aber
0x0035
! Wenn das Ergebnis richtig sein soll, muss der Entwickler wissen daß Port 53 als short LE die Zahl 13568 verkörpert.
Nein, er muss seine C-Standard-Bibliothek richtig verwenden.
Viele Grüße
Robert
Gerade mal exerziert:
#include <stdio.h>
#include <stdint.h>
#ifdef WIN32
#include <Winsock2.h>
#else
#include <arpa/inet.h>
#endif
const uint16_t ui = 53;
int main(void) {
const uint16_t iu = htons(ui);
printf("%#x %#x\n", ui, iu);
fwrite(&ui, sizeof(ui), 1, stdout);
fwrite(&iu, sizeof(iu), 1, stdout);
return 0;
}
.\endian | xxd
00000000: 3078 3335 2030 7833 3530 300d 0a35 0000 0x35 0x3500..5..
00000010: 35 5
(Ja, unter Windows gerade.) Aber schau dir mal den Teil nach dem Newline an: Da steht einmal 35 00
, das ist deine 53 in Host Byte Order. Und das folgende 00 35
ist der in die Network Byte Order konvertierte Wert. Das funktioniert wie beschrieben.
Viele Grüße
Robert
Hallo pl,
> printf("%#x", htons(53) ); // 0x3500
Rauskommen muss aber 0x0035
Hast Du das auf einer Big Endian Maschine ausgeführt? Dann müsste 0x0035 herauskommen. Auf eine LE-Maschine ist 0x3500 korrekt. Weil auf einer LE-Maschine 0x3500 als Bytefolge 0x00 0x35 gespeichert wird, was der BE-Bytefolge für 53 entspricht. Wie schon mehrfach gesagt: auf einer BE Maschine sind die hton-Funktionen No-Op Funktionen.
Die Idee ist doch, dass Du in deiner Software aus Effizienzgründen grundsätzlich mit den ints und der endianness arbeitest, die für die Ausführungsmaschine nativ sind. Nur dann, wenn Du eine definierte Endianness brauchst, konvertierst Du. D.h. genau am Rand deines Systems. Und die Implementierung von htons/ntohs ist so gemacht, dass Du nicht wissen musst, ob Du auf einer BE oder LE Maschine bist. Ruf einfach die Funktion auf. Und es passiert das richtige. Sozusagen Polymorphie in der C-Lib.
Rolf
hi Rolf,
wenn ich in C die Zahl 53 als 16 Bit BE haben will, notiere ich 0x00 und 0x35 in 2 Oktetten und gut is. Und so muss ich in C mit einem DNS Header verfahren:
Sho h[6] = {
htons(0xACDC), // Identyfier
htons(0x0100), // Standard Query
htons(0x0001), // One Question
0, 0, 0 // Answer RRs, Authority RRs, Additional RRs
};
Weil ich weiß daß C intern ein 0xACDC als LE auffasst, also muss ich das rumdrehen. Mit Perl, was auf derselben Maschine läuft, ist eine solche Konvertierung nicht notwendig, wenn ich da $h[0] = 0xACDC;
notiere kommt das mit derselben Byteorder am Server an.
Mit
printf("%#02x ", $_) for unpack("W*", pack L=>0x12345678);
kannst Du übrigens testen, welche Architektur vorliegt, zum Ergebnis siehe perldoc -f pack
MfG
Hallo pl,
wenn ich in C die Zahl 53 als 16 Bit BE haben will, notiere ich 0x00 und 0x35 in 2 Oktetten und gut is.
Solange Du das als Konstante hast, klar.
Aber das ist ja selten der Fall. Wenn da ein Längenfeld ist, das BE gespeichert werden muss, sieht's wieder anders aus. Und dafür ist dann htons da.
Rolf
hi Rolf,
selbstverständlich tun htons() & Co. das was sie sollen.
Das Problem ist jedoch, daß man einen 16 Bit BE in C nicht in einem uint16_t ablegen sollte wenn der Prozessor diesen uint16_t als LE auffasst. Nicht einmal mit einem dicken Kommentar dahinter würde ich das tun!
MfG
Moin,
Das Problem ist jedoch, daß man einen 16 Bit BE in C nicht in einem uint16_t ablegen sollte wenn der Prozessor diesen uint16_t als LE auffasst. Nicht einmal mit einem dicken Kommentar dahinter würde ich das tun!
dann erklär mir mal bitte, was daran falsch sein sollte:
const uint16_t someNo = 42;
uint16_t someNwNo = htons(someNo); /* someNo in network byte order */
printf("Writing to network something #%u\n", someNo);
write(sockh, &someNwNo, 2);
Viele Grüße
Robert
Alles gut @Robert B.
solange Du ein htons(mynumber);
unmittelbar vor der Ausgabe notierst, kann das auch jeder nachvollziehen. Was ich meinte ist, daß man ein uint16_t host = htons(mynumber);
nicht machen sollte!
MfG