Anfängerproblem mit C

#8032715
Lesenswert?

Hallo,

ich bin blutiger Anfänger in C und habe gleich ein Verständnis Problem

1
void fn1(int *j){
2
int i;
3
i = *j;  // i ist 123 ok
4
*j = *j + 1; // der inhalt der Speicherstelle auf die j zeigt + 1 ok
5
i = *j;  // i ist 124 ok
6
j++;  // adresse +4 ok
7
*j++; // adresse +4 warum?
8
i = *j;  // i ist irgendwas ok
9
}
10

11
int main(void) {
12
  int k=123;
13
  fn1(&k);
14
  return 0;
15
}

Warum wird bei *j++ die Adresse erhöht und nicht der Inhalt der Speicherstelle?

Danke im voraus

#8032741
Lesenswert?

Holger schrieb:

(*j)++; getestet und funktioniert.

Am besten gewöhnst du dir unnötig klammernsparende Schreibweisen gar nicht erst an. Wenn du nicht tagtäglich immer nur programmierst, unterlaufen dir sonst leicht mal Fehler. Oder du verlierst Zeit, weil du beim Suchen von (anderen) Fehlern immer wieder mal überlegen/nachschlagen musst, ob nicht diese Schreibweise der gesuchte Fehler ist.

Stichwort: Defensive Codierung.

Der Compiler übersetzt ohnehin unabhängig von der Schreibweise, es spart also nichts.

: Bearbeitet durch User
Persönliche Seite #8032748
Lesenswert?

Holger schrieb:

i = *j; // i ist irgendwas ok

Ist nicht ok, ist "undefined behavior" (UB) und kann irgendwas bewirken, z.B. Programmabsturz. Oder der Compiler entfernt einfach die main()-Funktion komplett, weil er davon ausgehen darf dass UB nie auftritt und somit die main() Funktion sowieso nie aufgerufen wird, weil sie immer zu UB führt. Das gilt auch für Lesezugriffe.

#8032769
Lesenswert?

Niklas G. schrieb:

Ist nicht ok, ist "undefined behavior" (UB) und kann irgendwas bewirken, z.B. Programmabsturz.

Aber nur im Kontext der von Dir nicht zitierten vorangegangenen Codezeilen.

Für sich alleine betrachtet ist die von Dir zitierte Zeile 8 vollkommen legitimer Code, nämlich 'ne simple Pointerdereferenzierung.

"Kriminell" wird es durch die Zeilen 6 und 7 des Codes aus dem Eröffnungsthread, die den Pointer auf etwas anderes zeigen lassen als beim Aufruf der Funktion, der der Pointer übergeben wurde.

#8070006
Lesenswert?

Georg W. schrieb:

Ich dachte sizeof(INT) ist plattformabhängig und nicht generell 4.

Ist es auch nicht.

Man kann es getrost als Fehler betrachten, hier "generell" anzunehmen.

Auf 8- oder 16-Bit-Systemen ist ein /int/ üblicherweise 16 Bit groß, auf 32-Bit-Systemen ist er üblicherweise 32 Bit groß, auf 64-Bit-Systemen ist er meistens auch 32 Bit groß, kann aber auch 64 Bit groß sein.

Georg W. schrieb:

Wir programmieren SPSen in ANSI C und da ist INT nur 2 byte groß.

Das liegt nicht an "Ansi-C" (das nennt man heutzutage C89), sondern daran, daß da entweder 8- oder 16-Bit-CPUs drinstecken.

#8070034
Lesenswert?

Udo K. schrieb:

Wenn wir schon beim klugscheissen sind...

INT ist immer 4 Bytes. Das ist ein typedef in Windows.

Wenn wir schon beim Klugscheissen sind, hast Du dich grad selbst widerlegt.

sizeof(int) ist Plattformabhängig. Und bei Windows halt grad mal 4. int32_t ist plattformunabhängig, nicht zufälligerweise immer und überall 4 Byte und das soll man auch verwenden

Persönliche Seite #8070100
Lesenswert?

Die ganze Streiterei um C und seine Regeln und Definitionen erinnert mich einfach immer wieder an dieses Meme. Egal wie perfekt jemand C kann, irgendwann übersieht er was und schießt sich dann voll selbst ins Knie. Ohne Ausnahme.

Grüße von einem Rust-Coder, der sich lange genug mit der C-Grütze rumgeschlagen hat und jetzt die Show genießt.

Angehängte Dateien:
#8070131
Lesenswert?

Speichernavigation sollte man erst weiter hinten anfangen. Das ist besser, denn da kommen viele Anfänger durcheinander, wenn die nicht die Grundlagen erarbeitet hatten. Ich hatte damals auch Probleme (weil: da kommt doch vieles auf einmal auf einen zu) und so brauchte ich so um die 2-3 Wochen, um da lockerer draufzuschauen. Vielleicht ist es besser, erstmal mit Stackoverflow (nicht die Internetseite) und gdb in die Speichergeschichte einzusteigen. Kann jedenfalls auch hilfreich sein. Hinsichtlich der Sonderzeichen ist das Erlenkötterbuch gut - da muss man aber bei der Stange bleiben, damit man die Zusammenhänge behalten kann.

#8070136
Lesenswert?

Niklas G. schrieb:

i = *j; // i ist irgendwas ok

Ist nicht ok, ist "undefined behavior" (UB) und kann irgendwas bewirken, z.B. Programmabsturz. Oder der Compiler entfernt einfach die main()-Funktion komplett, weil er davon ausgehen darf dass UB nie auftritt und somit die main() Funktion sowieso nie aufgerufen wird, weil sie immer zu UB führt. Das gilt auch für Lesezugriffe.

das glaube ich erst Mal nicht (ohne es zu wissen): wenn der Compiler main übersetzt, weiß er doch nicht dass in fn1 ein undefined behavior auftritt (fn1 könnte ja auch in einem anderen File sein)

Persönliche Seite #8070138
Lesenswert?

Walter S. schrieb:

Compiler main übersetzt, weiß er doch nicht dass in fn1 ein undefined behavior auftritt (fn1 könnte ja auch in einem anderen File sein)

Ich hab ja nicht gesagt dass der Compiler das UB erkennen muss. Aber wenn er es erkennt, darf er das Programm beliebig verunstalten ohne den C-Standard zu verletzen. Der Standard verlangt nicht vom Compiler, fehlerhaften Code irgendwie sinnvoll zu übersetzen.

Wenn fn1 in der selben Datei steht wie hier im Beispiel, sieht der Compiler was darin passiert. Wenn's eine andere Datei ist sieht er es auch, wenn LTO aktiviert ist.

#8070152
Lesenswert?

Nick schrieb:

Von dir?

BTW: Ich war es nicht, der mit INT dahergekommen ist. Die Frage des TO war plattformunabhängig gestellt.

Ja, offensichtlich von mir :-)

INT gibt es im C-Standard nicht.
C unterscheidet zwischen Groß- und Kleinschreibung.

INT ist ein in Millionen von Zeilen verwendetes Typedef, das immer 4-Bytes hat.

#8070173
Lesenswert?

Udo K. schrieb:

INT ist ein in Millionen von Zeilen verwendetes Typedef, das immer 4-Bytes hat.

Quatsch. Du kennst nur nicht genügend Prozessorarchitekturen:

1
#include <stdio.h>      // printf
2

3
void main( void)
4
{
5
    int i, j;
6

7
    i = 0;
8
    j = 1;
9
    while( j > i)
10
    {
11
        i++;
12
        j++;
13
    }
14
    printf( "%d %d\n", i, j);
15
}
1
32767 -32768
#8070184
Lesenswert?

Walter S. schrieb:

das glaube ich erst Mal nicht (ohne es zu wissen): wenn der Compiler main übersetzt, weiß er doch nicht dass in fn1 ein undefined behavior auftritt (fn1 könnte ja auch in einem anderen File sein)

fn1() übergibt einen Zeiger auf einen /int/ Wert. NUR EINEN! Wenn nun dieser Zeiger inkrementiert wird:

1
j++;  // adresse +sizeof(POINTER)

dann zeigt j hinter den angedachten /int/ Wert. Also ist:

1
i = *j;

ein Zugriff über einen ungültigen Zeiger.

#8070191
Lesenswert?

Udo K. schrieb:

Ja, offensichtlich von mir :-)

Ich weiß. :-)

Udo K. schrieb:

INT ist ein in Millionen von Zeilen verwendetes Typedef, das immer 4-Bytes hat.

Wo ist das immer 4 Btes groß? Bei dir? In wie vielen Milliarden Zeilen C-Code NICHT?

Mir ist schon klar, dass das K&R-C unzulänglich ist/war und dass von dort die Ursprünge des Gewurstles, das sich über Dekaden hinzieht, herkommt. "Inzwischen" kann man aber die Typen verwenden, die klar in der Größe definiert sind. Und das sollte man auch.

Bitte dazu mal <stdint.h> lesen, bzw. Kommentare/Erklärungen dazu.

Anekdote: Ich hatte mal mit dem Propeller von Parallax rumgespielt. In interessanter 32 Bit Prozessor mit einer verkorksten Sprache "Spin". Da war ein Integer Typ "word". Irgendwann wundere ich mich, warum der nur 16 Bit groß ist und frag im Forum nach. Bis dann der Entwickler des Propeller (Gracey ?) geantwortet hat, dass "word" immer 16 Bit lang ist. Auf die Frage woher denn die Definition kommt: Er hat den Compiler für Spin in Assember (total gaga, aber Respekt für die Arbeit) 8086 (jedenfalls ein 16-Bitter) geschrieben und da ist das halt ein word. Den Compiler mussten sie komplett neu schreiben, ASM ist nicht sonderlich portabel. Spin wurde dann um C ergänzt, der Propeller verschwindet so langsam in der Versenkung. Bis auf eine extrem engagierte Fan-Gemeinde.

long, double, long long, unsigned char, das ist völlig ballaballa. Leute, schreibt euren code plattformunabhängig und leserlich.

Ah, und zum Rust-Kommentar: Ja, da geb ich dir Recht. Der Designer hatte wohl genug unter C gelitten. Ich warte noch darauf, dass Rust endlich mal auf µC implementiert wird. Der eine STM32 (oder was) ist reichlich wenig um sich da drauf einzulassen. Ich wäre aber sofort dabei! Hab aber Rust seit zwei Jahren nicht mehr angefasst.

#8070199
Lesenswert?

Udo K. schrieb:

Das weisst du doch nicht.

Das steht im Sourcecode.

1
  int k=123;
2
  fn1(&k);

Und wenn ich mit einem array arbeite, dann mache ich sichtbar:

1
#include <stdio.h>
2
int arr(int j[], unsigned long n) {
3
    return j[n];
4
}
5
int main(void) {
6
    int array_of_ints[] = {3,5,7,11};
7
    for (unsigned long idx = 0; idx < sizeof(array_of_ints)/sizeof(int); idx++) {
8
        printf("%d\n", arr(array_of_ints, idx));
9
    }
10
    return 0;
11
}
: Bearbeitet durch User
#8070230
Lesenswert?

Nemopuk schrieb:

Ich meine, man kann sich bei C darauf verlassen, dass int mindestens 16 Bit groß ist.

/short/ ist (aus dem Gedächtnis) auch als <= /int/ und /long/ als >= /int/ definiert. Und /short/ muss < /long/ sein. Das ist allerdings alles relativ… ;-)

/int/ könnte also 16/32Bit und vielleicht sogar bald 64Bit (bei den demnächst erscheinenden 128Bit Prozessoren, Speicher kostet ja gerade nichts) breit sein. ;-)

: Bearbeitet durch User
Persönliche Seite #8070250
Lesenswert?

Rick schrieb:

Johann L. schrieb:

Und wo ist da INT?

Zeile 5: int i, j;

Die steht "int" und nicht "INT"!

Übrigens word das printf u.U. nie erreicht (Signed Overflow).

Komisch, bei mir schon. Die resultierende Ausgabe habe ich oben mitgepostet...

Dass ein Programm das macht, was du erwartest, bedeutet nicht, dass es korrekt ist :-)

Die Logik ist:

Programm macht nicht was du erwartest => Programm ist inkorrekt

und

Programm ist korrekt => Programm macht was du erwartest

Was NICHT gelten MUSS:

Programm ist inkorrekt => Programm macht nicht was du erwartest

sowie

Programm macht was du erwartest => Programm ist korrekt.

Komisch, bei mir schon.

Weil Undefined Behaviour kein definiertes Verhalten erfordert

  • Programm kann das machen was du erwartest (was immer das ist)
  • Programm kann nicht-funktional sein.
  • Compiler kann eine Diagnostic ausgeben
  • Compiler kann terminieren
  • Programm kann sich aufhängen / abstürzen.

Letzteres passiert zu Beispiel mit GCC 13. Und mit Sanitizer:

$ gcc x.c -Os -o x.x -fsanitize=undefined && ./x.x x.c:11:10: runtime error: signed integer overflow: 2147483647 + 1 cannot be represented in type 'int'

Was geht ist -fwrapv, also modular wrap-around:

$ gcc x.c -Os -o x.x -fwrapv && ./x.x 2147483647 -2147483648

#8070251
Lesenswert?

Norbert schrieb:

Wusste gar nicht, dass wir nun bei Rust sind. ;-)

Nein, sind wir nicht. Da Rust schon was mit C gemeinsam hat und gleichzeitig völlig anders ist ... An der Speicherverwaltung wird man sich in Rust (aber auch weitaus unbewusster und einfacher in C) zuerst die Zähne ausbeissen.

Damit hat Rust aufgeräumt, weil es in C saugefährlich ist (siehe oben) aber gleichzeitig schon auch recht praktisch ist. Der typgrößenwirrwar von C lässt sich leider nur durch komplettes ignorieren der alten Herangehensweise beheben. Und ein gutes Beispiel dafür wie es richtig ist, ist halt Rust. Oder/und exklusiv <stdint.h> zu verwenden.

Es hilft schon etwas, andere Sprachkonzepte anzuschauen. Da gibt es gute Lösungen an denen man sich orientieren kann.

#8070260
Lesenswert?

Nick schrieb:

Es hilft schon etwas, andere Sprachkonzepte anzuschauen. Da gibt es gute Lösungen an denen man sich orientieren kann.

Verstehe worauf du hinaus willst, aber gerade für den von dir zitierten Anfänger kann das aber nur noch verwirrender sein. Wenn man /C/ dann einigermaßen im Griff hat und wenn sich die Notwendigkeit ergibt, dann schaut man sich (mit diesem Grundlagenwissen) Anderes an. Python nutzt zB. nur eine int Klasse, die aber praktisch beliebig groß werden darf.

1
#!python
2
#!/usr/bin/python3
3
# vim: fileencoding=utf-8: ts=4: sw=4: expandtab:
4
from math import ceil,log10
5
for n in range(1,8):
6
    print(f'# {n}^{n}^{n}: int-Länge: 10^{ceil(log10(n**n**n))}')
7
# 1^1^1: int-Länge: 10^0
8
# 2^2^2: int-Länge: 10^2
9
# 3^3^3: int-Länge: 10^13
10
# 4^4^4: int-Länge: 10^155
11
# 5^5^5: int-Länge: 10^2185
12
# 6^6^6: int-Länge: 10^36306
13
# 7^7^7: int-Länge: 10^695975

Das ist für Anfänger in /C/ ähnlich wenig hilfreich wie Rust.

#8070273
Lesenswert?

Norbert schrieb:

Python nutzt zB. nur eine int Klasse, die aber praktisch beliebig groß werden darf.

Das ist halt eine andere Lösung. C ist ein "Hochsprachenassembler", wo man oft genug Platz sparen muss oder auf die Hardware eingehen muss (Ports mit 8 I/O etc.). Wenn man C so lernen will, dass int immer richtig ist, dann entdeckt man irgendwann, dass man long braucht. Also bin ich der Meinung, dass man es von Anfang an richtig lernen soll. Dass es streng definierte Typen gibt und man damit ganz gut zurecht kommt.

C ist auf µC sehr gut aufgehoben. Für dem Desktop gibt es besser geeignete Sprachen.

Wenn einem das nicht gefällt, dann soll er halt Python hernehmen. Oder Java, oder Rust, oder C++, oder Swing, oder C#. Das hat alles seine Vor- und Nachteile. Es ist aber nicht hilfreich, die Sprachkonzepte/Paradigmen zu ignorieren oder sie nicht zu akzeptieren. Man will ja mit einer Sprache programmieren, nicht gegen.

Man kann ja mal versuchen C in OCamel zu übersetzen. Oder gerne auch die andere Richtung.

#8070364
Lesenswert?

Johann L. schrieb:

In C/C++ heißt es eben nicht "int" sondern "_BitInt".

Na ja, aber man muss sich vorher festlegen und nach dem Kompilieren bleibt es statisch (wenn ich das richtig sehe). Das ist etwas völlig anderes als sich selbst dynamisch nach Bedarf anpassende Integer (wachsend und schrumpfend).

Des weiteren ist es auch noch arg limitiert. Um mal mein Beispiel in /C/ zu zeigen, für 7^7^7 braucht es 2311998 Bits

1
// log(2;7^7^7) = 2311998
2
//
3
_BitInt(2311998) xxx;
1
$ gcc -Wall -Wextra -pedantic -std=c2x -no-pie -Os -o x x.c -lpthread
2
x.c:8:1: error: ‘_BitInt’ argument ‘2311998’ is larger than ‘BITINT_MAXWIDTH’ ‘65535’
3
    8 | _BitInt(2311998) xxx;
4
      | ^~~~~~~

Ad1. clang könnte es wohl, aber nur eine Zeile weiter. Es würde dann eben bei 8^8^8 sterben.

: Bearbeitet durch User
#8070386
Lesenswert?

Norbert schrieb:

Das ist etwas völlig anderes als sich selbst dynamisch nach Bedarf anpassende Integer (wachsend und schrumpfend).

Ich nehme einfach mal an, dass es sowas (inzwischen) auch in C++ gibt.

In Rust (sorry) gibt es das auch:

1
use num_bigint::BigUint;

Das ist die dynamische Version. Gibt auch statische Versionen.

Man kann sich solche Sachen doch selbst bauen. Auch in C. Ich brauchte mal dynamische Arrays. Dann schreib ich mir die halt, das dauert keinen Tag und man kann die ab dann immer wieder verwenden.

"Das gibts nicht" ist eine etwas einfältige Einstellung.

#8070390
Lesenswert?

Nick schrieb:

Man kann sich solche Sachen doch selbst bauen.

Bauen kann man sich alles Mögliche. Da braucht man noch nicht einmal eine Programmiersprache, das geht auch in BASIC.

Hier geht es aber darum was eine Sprache (in diesem Fall /C/) zur Verfügung stellt, nicht was man hinterher nach- und dazubasteln kann. Übrigens geht es auch nicht um C++.

(Firma: 1984now) #8070395
Lesenswert?

Harald K. schrieb:

Es gibt auch Leute, die sehr lange Dinge machen, die sie nicht richtig können.

Oder halt auch Leute, die einfach nur Menschen sind und dementsprechend auch Fehler machen. Möchtegern-Göttern wie dir passiert so etwas natürlich niemals.

Das haben allerdings wohl auch all die anderen erfahrenen C-Programmierer gedacht, die die Fehler produziert haben: Ich kann das, ich mache keine Fehler.

Klar, denen kann man dann mit betreuten Programmiersprachen für "inklusives Programmieren" entgegenkommen

Natürlich ist das der sinnvollste Ansatz! Ein Hochsprache, in der Menschen programmieren sollen, hat es den Menschen so einfach wie möglich zu machen, keine Fehler zu produzieren, sonst taugt sie nix.

Wenn es z.B. sowas wie UB gibt, dann hat der Compiler die verdammte Pflicht, zu warnen und darf nicht einfach stillschweigend Code entfernen, weil ihm der überflüssig vorkommt. Er darf und sollte(!) einfach sagen, dass er ihm überflüssig vorkommt.

Alles andere ist menschenuntauglicher Bullshit.

Persönliche Seite #8070415
Lesenswert?

Nick schrieb:

Man kann sich solche Sachen doch selbst bauen. Auch in C.

Gibt's doch alles, als FOSS:

Ganze und Rationale Zahlen: GMP https://gmplib.org

Reelle Zahlen (Floating): MPFR https://www.mpfr.org

Complexe Zahlen (Floating): MPC https://www.multiprecision.org

GMP has a rich set of functions, and the functions have a regular interface. The basic interface is for C, but wrappers exist for other languages, including Ada, C++, C#, Julia, .NET, OCaml, Perl, PHP, Python, R, Ruby, and Rust.

Persönliche Seite #8070748
Lesenswert?

Nick schrieb:

Ich warte noch darauf, dass Rust endlich mal auf µC implementiert wird.

Clang/LLVM unterstützt Rust und AVR.

Und im GCC ist Rust in der Mache:

1
$ ../../source/gcc-master/configure --target=avr --enable-languages=c,c++,rust ...
1
$ avr-gcc -frust-incomplete-and-experimental-compiler-do-not-use x.rs -S -Os -mmcu=atmega8 && cat x.rs && cat x.s
2

3
#![feature(no_core)]
4
#![no_std]
5
#![no_core]
6
#[allow(dead_code)]
7
pub fn mymul (x: i16, y: i16) -> i16
8
{
9
   x * y
10
}
11

12
_ZN1x5mymul17h2076d231e17342eaE:
13
  movw r18,r24
14
  movw r26,r22
15
  rcall __mulhisi3
16
  movw r20,r22
17
  movw r22,r24
18
  mov r24,r21
19
  lsl r24
20
  sbc r24,r24
21
  mov r25,r24
22
  cp r24,r22
23
  cpc r25,r23
24
  breq .L1
25
  rcall abort
26
.L1:
27
  movw r24,r20
28
  ret
29
.ident  "GCC: (GNU) 17.0.0 20260626 (experimental)"

Die einzige Frage ist wieviele Leute es gibt, die sich mit Rust auskennen UND mit AVR auskennen UND mit Compiler-Interna auskennen UND die nötigen Resourcen (Lust, Zeit, ...) mitbringen, um da was zu bewegen.

Zumindest bei G++ ist diese Schnittmenge über die letzten 25 Jahren effektiv leer...

Persönliche Seite #8070787
Lesenswert?

Oliver S. schrieb:

Die AVR-Unterstzung in C/C++ war doch bisher eher theoretisch

Die C Unterstützung für AVR ist theoretisch? Vielleicht hast ja die letzten 25 Jahre verpennt :-)

Zu C++ schrub ich:

Johann L. schrieb:

Die einzige Frage ist wieviele Leute es gibt, die sich mit Rust auskennen UND mit AVR auskennen UND mit Compiler-Interna auskennen UND die nötigen Resourcen (Lust, Zeit, ...) mitbringen, um da was zu bewegen.

Zumindest bei G++ ist diese Schnittmenge über die letzten 25 Jahren effektiv leer...

Zu Clang/LLVM weiß ich nix, da hakelt es Teilweise schon mit C weil es keine dedizierte Libc gibt. Und Clang ist an vielen Stellen inkompatibel mit GCC, d.h. Piggypack auf AVR-LibC funktioniert nicht wirklich gut.

: Bearbeitet durch User

Antwort schreiben

Bitte melde dich an, um einen Beitrag zu schreiben.

oder

Mit Google-Account einloggen

Die Registrierung ist kostenlos und dauert nur eine Minute.

Jetzt registrieren