[🛈Start+]   [🥋Taekwon-Do+]   [🛀Autogenes Training+]   [🧮Mathematik]   [💡Physik]   [🔭Astronomie]
[🔑Impressum+]   [⚙️Technik+]   [📙Forschung & Lehre+] [🐧GNU/Linux+]   [📜LATEX]   Sprache:  [GB-NA-Flagge] 

JMB Blue Ribbon Icon (Freie Rede)


Programmieren in C++

unter GNU/Linux-Systemen mit GCC

[🐧GNU/Linux]   [📀Installation: G/L]   [📂Konfigs: G/L]   [🖬Ältere G/L-e]   [💾Progs: C++]   [⚠️Sicherheitsprobleme]

Inhaltsverzeichnis zu JMB's Seite über die Programmierung in C++ mit GCC unter GNU/Linux-Systemen


Basis zum Programmieren: GNU/Linux und GCC

Betriebssystem GNU/Linux als Programmierbasis

Es ist keine Frage, welches System sich für das Programmieren am besten eignet: 🐧GNU/Linux.
[Grafik mit TuX und GNU als Maskottchen von GNU/Linux]
Zunächst unterstützt kein anderes Beriebssystem so viele Architekturen und ist in so vielen Einsatzbereichen von Embedded Systemen (wie Settop-Boxen), über Raspberry Pi (dieser Einplatinencomputer wird z.B. auch an Schulen gerne verwendet), Smartphones/Tablets, Desktops, Servern, Mainframes bis hin zu Supercomputern zu Hause.
Dies ist kein Wunder, da Unix und somit auch Linux im Hinblick auf Portabilität (d.h. unproblematischem Einsatz auf unterschiedlichen Prozessorarchitekturen durch Verwendung der Programmiersprache C) entwickelt wurde und wird. So wurde vor Kurzem ein Großteil des Assemblercodes des Kernels Linux ersetzt, da der Performancevorteil mittlerweile durch bessere Compiler kaum ins Gewicht fällt, aber beim Portieren und Warten von unterschiedlichen Plattformen sehr viel Arbeit erfordert.
Die Wurzeln in freier Software wie GCC führen zu einer Vielzahl an Programmiermöglichkeiten, Libraries, Standards – und das ohne einen Cent, alles im Quellcode verfügbar, viele Anleitungen und Foren im Internet, ...

Erzeugen von Binaries mit der GNU‑Compilersammlung GCC

Hierbei spielt die GNU Compiler Collection (GCC) eine sehr gewichtige Rolle. Sie ist zentraler Bestandteil des GNU‑Systems, startete als reiner C‑Compiler und kann mittlerweile die Programmiersprachen:  C (gcc), C++ (g++), Objective‑C, Objective‑C++, Fortran (gfortran, Usage), Ada, D (gdc)𝔻. , Go (gccgo) und BRIG (HSAIL) übersetzen [mit cpp als C‑Präprozessor für C, C++ und Objective C] (vgl. Language Standards Supported by GCC) – zudem wird immer konkreter über eine Aufnahme der Sprache Rust diskutiert🏗️ .
Die Dokumentation der brand-neuen GCC‑Fassung 10.1 (vom 07.05.2020: Heise‑Developer, Phoronix oder LWN‑Artikel; Version 10 soll dann in Kubuntu 20.10 STS Einzug halten) bzw. der sehr stabilen Vorversion GCC 9.3 (vom 12.03.2020; zuvor 9.2 vom 12.08.2019; zuvor GCC 9.1), ebenso im entstehenden Kubuntu 20.04 LTS Focal in frischer Form enthalten (als gcc 9.3.0) sowie in Xubuntu 19.10 Eoan (als gcc 9.2.1), ist auf Englisch im Internet verfügbar: Online Documentation (für Versionen 10.1 bis 2.95.3 – sowohl in HTML‑Form als auch im PDF‑Format; letzteres natürlich aus 📜TEX/LATEX‑Quellen erzeugt). Die frisch erschienene GCC 10 verfügt u.a. über einen `Static Analyzer' (vgl. Phoronix, 14.01.2020) sowie als eine letzte Änderung über die Option -std=c++20 nach Finalisierung von C++20 als DIS, auch wenn die Arbeit dessen Implementierung in GCC noch nicht abgeschlossen ist (siehe Phoronix, 16.02.2020).
Mit LLVM/Clang gibt es zwar belebende Konkurrenz (unter Intel x86_64 liegt er mit GCC dicht auf, bei IBM POWER9 liegt Clang aber 20% zurück [Phoronix, 23.07.2019]; viele Architekturen werden nicht unterstützt [laut Homepage produktiv für X86‑32, X86‑64 und ARM nutzbar]; aber Clang ist erste Wahl auf macOS – so wie Microsoft Visual Studio erste Wahl auf Windows ist), aber weder ist Clang Freie Software wie GCC, noch ist sie auf so vielen Plattformen und für so viele Architekturen verfügbar (und erst mit Clang 9/10 ist nach Unterstützung von asm goto der Mainline‑Kernel für die x86_64‑Plattform größtenteils compilierbar [das amdgpu‑Problem soll ab Kernel 5.4‑rc1 behoben sein], zudem liegen die Benchmarks sehr dicht beisammen [vgl. Phoronix, 12.09.2019]; Härtung der beiden Compiler LVM/Clang und GCC behandelt der LWN-Artikel; dennoch ist ein generelles Übersetzen des Linux‑Kernels mit Clang trotz viel Bemühungen noch nicht gelungen; wohl aber in einigen Bereichen wie z.B. Android bereits etabliert).
Ebenso unterstützt GCC die jüngsten Standards – und auch wenn es ggf. verwundert, C und C++ werden ständig erweitert. Ein aktueller GCC unterstützt C++17 (mit Parameter -std=c++17, bzw. -std=gnu++17 für GNU‑Erweiterungen jenseits C++17; einige Bibliotheksmerkmale fehlen allerdings oder sind unvollständig) und zum Teil sogar C++20, wobei als Status bei beiden noch experimentell genannt wird, da sie immer noch recht jung sind. Der g++‑Default ist allerdings aktuell C++14. Einen genaueren Überblick über die eingehaltenen Standards liefert C++ Standards Support in GCC.
GCC enthält die Standard‑Bibliothek, Compiler, Linker, Assembler, Disassembler, ... dennoch werden zum Programmieren noch Komponenten benötigt bzw. können das Programmieren deutlich erleichtern, was in den nächsten drei Unterabschnitten behandelt wird.

𝔻 ) Die Programmiersprache D mit objektorientierten, imperativen sowie deklarativen / funktionalen Sprachelementen wird seit 1999 von Walter Bright entwickelt, der 2001 die erste Version (D1) des Compilers veröffentlichte – 2007 wurde diese unter Mitwirkung von Andrei Alexandrescu durch D2 abgelöst (und entwickelte sich seither inkrementell in über 100 Veröffentlichungen: aktuell ist 2.088.0 vom 01.09.2019); die D‑Standardlaufzeitbibliothek namens Phobos wurde vom 2006 einsteigenden Andrei Alexandrescu entwickelt. D übernimmt die meisten Sprachmittel der Sprache C, verzichtet im Gegensatz zu C++ aber auf die Kompatibilität dazu (hält aber die ABI‑Kompatibilität, so dass alle Programme und Bibliotheken, ggf. über Funktionen und Wrapper, nutzbar bleiben – die Anbindung von C++-Code unterliegt dagegen Einschränkungen). Programme können in D ohne Zeiger geschrieben werden – anders als Java ist es aber dennoch möglich, Zeiger bei Bedarf nahezu wie in C zu benutzen und so maschinennah zu programmieren. D verfügt über Klassenvorlagen und überladbare Operatoren, bietet Design by contract und Module und besitzt Sprachmittel wie integrierte Unit‑Tests und String Mixins; Automatische Speicherbereinigung (Garbage Collector) ist im Gegensatz zu z.B. C/C++ fester, wenn auch prinzipiell optionaler Bestandteil der Sprache.  DMD, der Digital Mars D‑Compiler, bildet die Referenzimplementierung, von Walter Bright für die x86/x86-64‑Versionen von Windows, Linux, macOS und FreeBSD erhältlich. Der GNU D Compiler GDC verbindet das Frontend von DMD mit dem GCC‑Backend (ab GCC 9.1), so wie der LLVM D Compiler LDC das Frontend von DMD mit dem LLVM‑Backend verbindet. Ebenso wurden die Compiler Dil und Dang in D selbst programmiert, die kompatibel zu LLVM sind. Mit Poseidon existiert sogar eine in D geschrieben IDE, die Autovervollständigung sowie Refactoring unterstützt und einen integrierten Debugger bietet. GNU Debugger und WinDbg unterstützen D rudimentär (Stand: Ende 2019).
Weitere Infos siehe Wikipedia oder z.B. dem Artikel im Heise‑Magazine: iX, 12/2019, S. 150.

🏗️ ) Rust ist eine recht neue, von Mozilla Research entwickelte und 2010 erschienene Sprache (erste stabile Version 2015). Diese Programmiersprache, die sicher, nebenläufig und praxisnah sein sollte, habe ich zwar weiter unten nicht als eine meiner Empfehlungen aufgeführt, wohl aber bzgl. der Ranglisten der Programmiersprachen mit deutlichem Vorsprung vor Go herausgehoben.
Eine der Gründe liegt am immer wieder anvisierten möglichen Einsatz von Rust für Linux-Systemtreiber (siehe z.B. LKML vom 27.04.2019 oder den LWN‑Beitrag vom 29.08.2019 zur positiven Aufnahme zukünfigen Codes in Rust durch Greg Kroah‑Hartman), was ebenfalls deutlich in einer wissenschaftlichen Studie anklang, nach der die meisten Sicherheitsprobleme in den Treibern zu finden seien (siehe Preprint: arxiv.org/1909.06344 vom 13.09.2019).
Ende 2019 gab es erneute Diskussionen, ein offizielles Frontend für Rust in GCC einzuführen – bisher gibt es lediglich Out‑Of‑Tree‑Fassungen (siehe den Phoronix‑Artikel vom 03.12.2013), wobei eine Aufnahme auch zur allgemeinen Sprachdefinition zu begrüßen wäre (vgl. Phoronix‑Artikel vom 28.12.2019). Wenn die Rust‑Entwickler mitzögen, könnte so das Henne‑Ei‑Problem elegant vermieden werden.

Fehlersuche im Binary mit dem Debugger GDB

Wenn ein Binary nicht das tut, was es soll, hat man meist als Autor eine Idee und findet das Problem im Quelltext – ggf. mit ein paar Codeveränderungen und Testen (Edit–Compile–Test‑Zyklus).
Ist es komplizierter, so hat man viele Möglichkeiten, sich dem Problem zu nähern, die zum Teil beim erzeugten Binary und dem Umgang mit diesem bereits skizziert sind.
Eine besondere Rolle fällt dabei (Edit–Compile–Debug‑Zyklus) dem Debugger zu, im GNU‑Projekt gleichbedeutend mit Debugger GDB, dessen kommende Version 9 u.a. Multi-Target Debugging Support bringen wird (vgl. Phoronix, 11.01.2020).
Beim aktuellen Stand, den dieses Dokument vermitteln soll, ist dessen Einsatz kaum nötig – ich werde hierzu aber mit hoher Wahrscheinlichkeit in Zukunft weitere Erläuterungen ergänzen.

Umgang mit Quelltexten: Editoren und IDEs

Was zum Programmieren aber in jedem Fall benötigt ist ein Editor oder eine IDE (Integrated Development Environment), um den Quelltext eingeben zu können.

Als Editor empfehle ich wie immer 🐧vim, wobei bei Programmierern eher emacs (echter Bestandteil des GNU‑Projekts – ebenso IDE; siehe Homepage) beliebt ist. Beide Editoren haben eine steile Lernkurve, wachsen aber hervorragend mit. Einfache Editoren haben den entscheidenden Nachteil, dass man später umsteigen muss; wer aber dennoch erst einmal diese Hürde nicht nehmen will, sei auf joe, gedit, jedit oder kate verwiesen.

Eine IDE würde ich zu Anfang nicht empfehlen, weil man dadurch eher abgelenkt wird und sich ggf. oberflächliches Vorgehen einschleift – genauso wie ich einen 📜WYSIWYG‑TEX/LATEX‑Editor ablehne. Dennoch hier einige Beispiele für eine IDE für C/C++‑Projekte: GNAT Programming Studio (GNAT, GPS Homepage), Code::Blocks, Lazarus, MonoDevelop, NetBeans, Eclipse CDT (C/C++ Development Tooling), CodeLite, Bluefish, Geany, Anjuta (GNOME), KDevelop (KDE; 02.02.2020 Phoronix‑Bericht oder auf Deutsch den Heise‑Bericht über die frische Version 5.5 bzw. 06.08.2019 Phoronix‑Bericht oder auf Deutsch den Heise‑Bericht über die vorige Version 5.4).

Automatisierte Erstellung von Binaries: Makefiles als Steuerdatei des Utilitys make

Sinnvoll ist aber der Gebrauch eines Makefiles (das vom Utility make verwendet wird; GNU make erschien am 19.01.2020 in Version 4.3, vgl. Phoronix & LWN; siehe dessen Einsatz unterhalb des Makefile‑Beispiels; vgl. GNU Autoconf), der die nötigen Eingaben auf ein sinnvolle Maß reduzieren kann (vgl. Eine Einführung in Makefiles, kleine Einführung für C und Makefiles, O'Reilly-Einführung in Makefiles, GNU make Manual) – hier ein sinnvolles kleines Beispiel für Makefile:

# ******************************************************************************
# * 'Makefile' des Verzeichnisses '~/mycpp/jmb/'                               *
# *                                                                            *
# * J.M.B.  [URL: https://www.jmb-edu.de/cpp_programming.html#makefileexamp]   *
# *                                                                            *
# * Lizenz:   (c) 2019 unter GPLv3: https://www.gnu.org/licenses/gpl-3.0.txt   *
# *                                                                            *
# * Erstellung:           05.08.2019                                           *
# *                                                                            *
# * Letzte Umgestaltung:  04.09.2019  (Phase 2; vgl. Phase 1 vom 08.08.2019)   *
# ******************************************************************************

# * Dafuer sorgen, dass 'all' und 'clean' auch dann ausgefuehrt werden,
#   wenn die jeweils entsprechende Datei existieren sollte:
.PHONY: all     clean   exampd  exampf  example_debug   example c       cf

# * Beim Hilfstext nicht durch doppelte Ausgabe verwirren, also das Kommando
#   nicht vor der Ausfuehrung ausgeben:
.SILENT:        info    help    h

# * Definitionen von sinnvollen Compiler-Flag-Kombinationen als Variable
#   (hier ist noch viel Raum fuer Verbesserungen - nur ein Anfang):

# o Debug Modus: optimierte Debugging-Faehigkeit mit allen Standard- sowie
#   allen Extra-Warnungen (zum Lernen beim Edit-Compile-Debug-Zyklus):
cppflagsd = -Og -Wall -Wextra

# o Space Modus: optimiert bzgl. Groesse und alles von -O2, das dem nicht
#   zuwider laeuft (falls man es mal brauchen sollte ;):
cppflagss = -Os

# o Final Modus: optimiert haerter bzgl. Kompilierzeit und Geschwindigkeit
#   des Binarys.
cppflagsf = -O2

# * Fuer Namensgebung der Backup-Datei Datum und Zeit als String besorgen:
bckdate := $(shell date +%Y%m%0e%0k%M%S)
bckname := ~/Downloads/mycpp_jmb_$(bckdate).tgz
humdate := $(shell date +%a.,\ %0e.%m.%Y,\ %0k:%M:%S)
humdatedate := $(shell date +%a.,\ %0e.%m.%Y)
humdatetime := $(shell date +%0k:%M:%S)

# ** Das nun kommende erste Target wird ausgefuehrt, wenn 'make' ohne
#    Argument, d.h. der Angabe des Targets, aufgerufen wird:

# * Hilfstext (zur Uebersicht, was alles gemacht werden kann; siehe Screenshot unten):
info help h:
                -echo "Zur Zeit sind die folgenden Parameter moeglich:         ^***************"
                -echo "  ep = ed = edit = e:                                     * Hilfs-Text *"
                -echo "    Quelltext des Programms mit vim editieren,             * zu 'make' *"
                -echo "  em:                                                     * von J.M.B. *"
                -echo "    Editieren von 'Makefile' mit vim,                   ^***********   *"
                -echo "  exampd = example_debug = c:                                      *   *"
                -echo "    Kompilieren & Linken von 'example_debug' & 'ls -l' vom Binary, *   *"
                -echo "  exampf =  example = cf:                                          *   *"
                -echo "    Kompilieren & Linken von 'example' und 'ls -l' vom Binary,     *   *"
                -echo "  all {= exampd & exampf}:                                         *   *"
                -echo "    Erzeugen beider Binaries: 'example_debug' & 'example',         *   *"
                -echo "  r:                                                               *   *"
                -echo "    Aufrufen von 'example_debug args' & Fehlercode ausgeben,       *   *"
                -echo "  run:                                                             *   *"
                -echo "    Aufrufen von 'example args' - danach Fehlercode ausgeben,      *   *"
                -echo "  clean:                                                           *   *"
                -echo "    Hausputz (: 'example_debug' loeschen),                         *   *"
                -echo "  superclean:                                                      *   *"
                -echo "    Fruehlingsputz (% 'clean' + loeschen von 'example'),           *   *"
                -echo "  backup = bck                                   **** [jmb-edu.de] *   *"
                -echo "    Erstellen einer tgz-Sicherung,               *  ****************   *"
                -echo "  help = h = info = '' {d.h. ohne Target}:       * Have Fun with GNU!  *"
                -echo "    Diesen Hilfstext ausgeben.                   ***********************"
                -echo "  == GNU 'make' ${MAKE_VERSION} gestartet am $(humdatedate), um $(humdatetime) Uhr ! =="

# * Was man unter `mach alles' verstehen soll (z.B. mehrere Binaries bauen;
#   dieses Target ist zumeist das erste und wird bei 'make' ausgefuehrt):
all:    exampd  exampf

# * Sprungpunkt/Target fuer das Programm (Name und was damit passieren soll):
# -a) Optimiert fuer optimale Debbuging-Faehigkeit, allen Standard-
#     sowie den Extra-Warnungen (zum Lernen beim Edit-Compile-Debug-Zyklus):
exampd c example_debug:         example.cpp
                -g++ $(cppflagsd) example.cpp -o example_debug
                -ls -l example_debug

# - b) Optimiert fuer Kompilierzeit und Geschwindigkeit des Binarys:
exampf cf example:              example.cpp
                -g++ $(cppflagsf) example.cpp -o example
                -ls -l example
# ... fuer weitere Verbesserungen siehe z.B.:
#  https://developers.redhat.com/blog/2018/03/21/compiler-and-linker-flags-gcc/

# * Editieren:

# o ... des Programm-Quellcodes:
ep ed edit e:
                -vim example.cpp

# o ... des Makefiles:
em:
                -vim Makefile

# * Binary aufrufen, danach Fehlercode auslesen (0 = OK; sonst = KO):
r:
                -./example_debug $(filter-out $@,$(MAKECMDGOALS)); echo "Fehlercode: "$$?" !"

# * Fuer andere als die ausgewiesenen Targets mache nichts
#   (wegen r/run-Optionen noetig, sie sonst je eine Fehlermeldung gaeben) -
#   Vorsicht: 'make quark' macht nichts - auch keine Fehlermeldung!:
%:
                @:

run:
                -./example $(filter-out $@,$(MAKECMDGOALS)); echo "Fehlercode: "$$?" !"

# * Waere Compilieren und Ausfuehren gewuenscht, dies auskommentieren:
#cr:    c       r

# * Hausputz (Problemreste und Debug-Binary werden geloescht):
clean:
                -rm -f core *.o *.bak *~ example_debug

# * Fruehlingsputz (auch das finale Binary wird nun geloescht):
superclean:     clean
                -rm -f example

# * Sicherung (als gezipptes tar-Archiv):
backup bck:     clean   exampd
                -echo "Sichere in: '$(bckname)' ..."
                -cd .. ; tar cfvz $(bckname) jmb/ ; cd jmb

# Zu beachten ist, dass nach "TARGET:" bzw. am leeren Zeilenanfang Tabs
# stehen muessen - Leerzeichen funktioniert nicht - ebenso zwischen
# multiple targets, die von einem anderen (hier 'all' oder 'bck')
# aufgerufen werden!
# Somit darf man auch nicht Copy&Paste machen (Blanks werden dann durch
# Leerzeichen ersetzt), sondern kopieren oder ueber 'tar' sichern ...

# ******************************************************************************
# * Das war's ...  Viel Spass beim eigenen Experimentieren und Programmieren ! *
# ******************************************************************************

Nun kann mit make example (oder make example_debug; per make all wird beides gemacht) das Beispielprogramm example.cpp übersetzt werden, der Hausputz macht aktuell noch wenig (da core, *.o und *.bak nicht existieren; lediglich example_debug würde gelöscht), make superclean löscht zusätzlich das Binary (d.h. die ausführbare Datei in Maschinensprache), und make backup erstellt ein Backup des Verzeichnisses jmb. Noch wirkt es nicht wie eine notwendige Ersparnis, aber schon aufwendig ausgeklügelte Compiler-Flags (vgl. insb. Optimierungs-Optionen oder Warnungen – oder auch die Red Hat Empfehlungen für Compiler- und Linker‑Parameter der GCC) oder mehrere zu übersetzende Module bringen dadurch eine enorme Erleichterung sowie Ordnung, Dokumentation und Übersicht. Der Hauptzweck von make liegt im automatischen Erzeugen lauffähiger Programme, bei der ggf. viele Dateien übersetzt und gelinkt werden müssen, d.h. Compiler und Linker stehen im Zentrum. Zudem hat man heute viele Auswahlmöglichkeiten, welche Anforderungen das Binary erfüllen soll. Diese werden über Parameter, den sogenannten Compiler‑Flags, GCC mitgeteilt. Wenn man Fehler finden will, sollten Debugginghilfen eingebaut sein, die sich eher nachteilig auf die Größe und/oder die Ablaufgeschwindigkeit auswirken werden. Daneben kann man die Ablaufgeschwindigkeit optimieren – soweit, dass man ggf. bei einigen Binaries Fehler provoziert (d.h. nicht nur harte sondern sogar agressive Optimierung – ohne Rücksicht auf Verluste). Im Gegenzug können auch seltene Ausnahmebedingungen abgefangen werden oder Angriffsziele maskiert bzw. geschützt werden, was als Härtung des Codes bezeichnet wird – im Gegensatz zur Härte der Optimierung, die bei hart sozusagen im gelben Bereich und bei agressiv im roten liegt (der ggf. im Beispiel von schnellen Spielen mit nicht mehr perfekter Grafik durchaus akzeptabel sein kann, bei einem Dateisystem oder einer Datenbank mit fehlerhafter Verarbeitung aber inakzeptabel wäre). Diese Optimierung wird hier grob per Flags in den Zeile 22‑32 gesteuert und dann in Zeilen 75‑87 umgesetzt.
Aber man sieht, dass man den Namen nicht mehr kennen muss; man geht ins Projektverzeichnis, editiert die Quelldatei (wenn es nur eine ist, sonst braucht man geeignete Label) mit make edit, übersetzt die Codes mit make all und ruft zum Test das Binary mit make run auf (bzw. make r; vgl. zugehöriges Programm).
Der Bereich von Zeilen 99‑110 wurde am 23.08. ergänzt, um dem Binary auch einen oder mehrere Parameter beim Aufruf mitgeben zu können. D.h. statt im aktuellen Verzeichnis ./example 2147483646 einfach make r 2147483646; bei negativen Zahlen muss man ‑‑ als erstes Argumen von make verwenden, damit das führende - nicht als Argument von make missverstanden wird, d.h. anstatt ./example ‑5 aufzurufen (vgl. Programm-Output unten), kann man nun auch make ‑‑ run ‑5 eingeben. Wer keine Parameter von make benötigt, kann auch alias make='make ‑‑' in eine ~/.alias Datei einbauen und von ~/.profile durch die am Ende anzufügenden Zeilen:

if [ -f "$HOME/.alias" ]; then
  . "$HOME/.alias"
fi

automatisch aufrufen lassen. Dann funktioniert auch make run ‑5.
Das Makefile steht unten mit dem Beispielprogramm example.cpp und den resultierenden Binarys example und example_debug zum direkten Download bereit.
Hier die Ausgabe der Hilfsseite von der aktuellen Makefile‑Datei, die mit make info (oder einfach mit make) ausgelöst wird:
[Output des Hilfstextes von Makefile]
Die Vorteile, die 🐧GNU/Linux, GCC und die GNU‑Kette der Hilfswerkzeuge bieten, sollten mit diesen Argumenten schon klar belegt sein.


Einstimmung in Programmiersprachen

Historische Entwicklung der drei Generationen

Eine Programmiersprache wird verwendet, um über verständlichere Tokens und eine logische Struktur einen Algorithmus bzw. Datenstrukturen zu implementieren, die der Rechner entsprechend verarbeiten kann, nachdem ein Compiler und Linker den Quelltext in ein Binärprogramm in Maschinensprache umwandelt, die der Computer ausführen kann (Interpreter können alternativ statt eines Compiler verwendet werden, sind aber langsamer, da die Erzeugung des Maschinencodes zur Laufzeit erfolgt).

Die ersten Programme wurden direkt in Maschinensprache über Schalter und später Lochkarten dem Computer eingegeben: 1GL (1st Generation Language).
Danach wurde Assembler verwendet, um besser zu merkende Mnemonics statt einer Kette von 0|1‑en eingeben zu können: 2GL (2nd Generation Language), wobei aber immer noch das Vorgehen des Computers direkt vorgegeben wird.
Erst danach wurden höhere Programmiersprachen, allen voran C, Fortran und Cobol, als erste Programmiersprachen der dritten Generation eingeführt: 3GL (3rd Generation Language).
Es gibt keine 4GL, außer im Marketing!

C als hardwarenahe Programmiersprache

Wegen der Nähe zu C++, um das es in den folgenden Abschnitten gehen wird, und seiner Bedeutung im 🐧GNU/Linux‑Umfeld soll hier zunächst kurz auf C näher eingegangen werden.
Die Programmiersprache C wurde 1969‑1973 von Dennis A. Ritchie (09.09.1941‑12.10.2011; er alleine ist für das C‑Design verantwortlich) in den Bell Laboratories (AT&T‑Forschungsabteilung) für die Programmierung des damals neuen Unix‑Betriebssystems entwickelt, wobei C aus der zuvor von Ken L. Thompson (*04.02.1043) und Dennis Ritchie 1969/70 geschriebenen Programmiersprache B entwickelt wurde. Für C ist noch Brian W. Kernighan (*01.01.1942) zu nennen, der als Co‑Autor des prominenten C‑Buchs und damit ersten C‑Standards bekannt ist.
Hier die Historie mit ausgewiesenen Normen dieser Programmiersprache:
1978Buch The C Programming Language: K&R C
1989C Norm ANSI X3.159‑1989: ANSI C / C89
1990C Norm ISO/IEC 9899:1990: C90 (=C89)
1995Minimale Erweiterung als C Norm ISO/IEC 9899/AMD1:1995: C95
1999C Norm {u.a. mit Ergänzungen von C++} ISO/IEC 9899:1999: C99
2011ISO‑Standard {u.a. bessere Kompatibilität mit C++} ISO/IEC 9899/2011: C11
2018Minimale Erweiterung als C Norm ISO/IEC 9899/2018: C18 bzw. C17
2022?? Erweiterung als C Norm ISO/IEC 9899/2022: {anfänglich C2x} C22

C wird auch dazu verwendet, von einer anderen Programmiersprache in C umgesetzten Code mit den auf quasi allen Plattformen existierenden C‑Compilern mit ausgeklügelten Optimierungen zu übersetzen, z.B. Chicken (Scheme), EiffelStudio (Eiffel), Esterel, CPython/PyPy, Sather, Squeak (Dialekt von Smalltalk) und Vala.

Anders als C++ verfügt C nicht über eine Standardbibliothek, dennoch gibt es einige übliche Bibliotheken. Unter Linux ist die wichtigste die GNU C Library (glibc oder libc; aktuell ist glibc 2.31 vom 01.02.2020, vgl. Originalhomepage und die Online Quellen weiter unten).

Empfehlung einer Programmiersprache

Wie bekannt sein dürfte, ist 🐧Linux von Linus Torvalds mit erster Version 0.01 vom 17.09.1991 entwickelt worden, später mit Version 0.12 im Januar 1992 unter GPL2‑Lizenz gestellt worden und mit sehr wenigen Ausnahmen (wie Assembler‑Code) vollständig in C geschrieben (ebenso die Desktops Gnome und XFCE oder das Bildverarbeitungsprogramm GIMP). Für die Kernel‑Programmierung und andere extrem effiziente Programme sieht er keinen Sinn in C++, wie er mehrfach ausführte.

Die Empfehlung einer Programmiersprache nach meiner persönlichen Meinung als bekennender Unix‑Spezialist, der immer noch mit der Wissenschaft verbunden ist, wäre CC++, Fortran – und ggf. noch Python 3🥈. 
Es gibt über 2500 Programmiersprachen (einen ersten Eindruck der schieren Fülle liefert bereits die Zeittafel der Programmiersprachen auf Wikipedia, obwohl hier nur ca. 7% enthalten sind), und man wird sich nur in einer richtig auskennen, so wie man nur eine Hauptsprache (zumeist die Muttersprache) hat. Man sollte also gut nachdenken – und nicht auf Java, C# oder unsinnige Zwänge wie objektorientiertes Programmieren oder agile Softwareentwicklung als ständige Verpflichtung hereinfallen. Leider werden Schüler häufig genau mit solchen schädlichen da oberflächlichen Dingen zuerst beworfen, so dass ihr Weg deutlich erschwert wird. Zumindest sollte jeder sich frühzeitig überlegen, was vorrangig die Programmiersprache der Wahl ist – und dies hängt vom Einsatzzweck ab.
Daneben gibt es große SW‑Projekte, die in mehreren Sprachen (typisch 2-4) vollzogen werden😵. 

🥈 ) Neben C, C++ und Fortran (1957; jüngste Norm ist Fortran 2018; von GCC unterstützt) verblassen die anderen – alle guten Dinge sind eben drei ;).
Nur mit Widerwillen habe ich Python 3 angegeben (Python wurde 1991‑94 entwickelt, die jüngste Hauptversion 3, ab 03.12.2008 verfügbar, ist inkompatibel zu den beiden Vorgängern, wobei Python 2 Ende 2019 mit EoL abtritt), weil es im schulischen Bereich die einzig halbwegs hochwertige Programmiersprache ist, die vermittelt wird, u.a. im Zusammenhang mit den Raspberry Pis.
Auch wenn es eine Zufallskoinzidenz ist, so sei dennoch erwähnt, dass die 3D‑Grafiksuite Blender, die auch für Spiele eingesetzt wird, in C, C++ und Python geschrieben wurde (vgl. auch Projekte in mehreren Programmiersprachen in nachfolgender Fußnote).
Auch wenn Rust interessant wirkt (2010‑2015 Entwicklung durch Mozilla zur ersten stabilen Version), würde ich diese von Mozilla entwickelte Sprache nicht direkt empfehlen, auch wenn es manche bereits jetzt ganz anders sehen: es muss sich erst noch zeigen, ob Rust die Zukunft der Systemprogrammierung werden wird wie im Beitrag vom 27.08.2019 behauptet (siehe auch LWN‑Kommentare).
Allerdings scheint mir das ähnlich gehypte Go (2009‑2012 Entwicklung durch Google zur ersten stabilen Version; von GCC unterstützt) eher ein Spielzeug zu sein, das allenfalls gegenüber Java oder C# überlegen ist.
Man kann auch Ranglisten von Programmiersprachen finden, wobei ich hier einige aus diesem Jahr (2019) erwähnen möchte – auch wenn ich gleichfalls vorausschicken sollte, dass man diesen Rängen keine hohe Bedeutung zuweisen sollte:
Bei der Menge an Programmiersprachen, den vielen Einsatzmöglichkeiten und den unterschiedlichen Qualitätsstandards sollte man diese Ranglisten nicht überbewerten. Allerding sind C und C++ wohl die beiden Sprachen, die jeder Kenner zu den Wichtigsten zählt, Rust ist das New Kid on the Block – Python löst auch kritische Stimmen aus, ist aber vielfach anwendbar. Java ist durch die Smartphones nicht zu verdrängen, hatte aber nicht einmal bei der Entwicklerfirma Sun jemals einen guten Ruf.
Und die Qualität  der Erzeugnisse (die in erster Linie vom Quellcode des Programms und der verwendeten Bibliotheken abhängt, aber ebenfalls vom Compiler, Linker, deren Flags beim Compilieren und schließlich auch vom Betriebssystem abhängt) hat bei mir eine Umdefinition von Begriffen ausgelöst:
eine App ist nichts Schickes oder Modernes, sondern etwas, das wenig kann und nur so benutzt werden kann, wie der Programmierer dies erdacht hat – also das Gegenteil von professionell.
Ein Programm ist qualitativ hochwertig, lässt sich vielseitig einsetzen und konfigurieren, dennoch effizient – also ein Qualitätsprodkt für unterschiedliche professionelle Workflows.
Eine Suite ist ein großes Programm, das eigentlich viele einzelne enthält (positive Beispiele wären LibreOffice [Schreibprogramm, Tabellenkalkulation, Präsentationsprogramm, Zeichenprogramm, Datenbank etc.] oder SeaMonkey / Mozilla Suite [WebBrowser, E‑Mail‑Programm, Kalender etc.]);
eine App Suite ist somit eher eine Müllhalde ...
Meine Kriterien sollten klar sein: keine zu junge Programmiersprache, keine die auf den Gnadenschuss wartet, viele Programmier-Techniken möglich macht und damit zur Demonstration und vernetztem Wissen führt, somit nur so viel Black-Box, wie nötig. C und C++ stehen beide im Ruf, schwer zu sein – dies muss aber nicht sein. Zumal man mit beiden nicht plötzlich feststellen muss, dass man ein Projekt nur mit einer anderen Sprache sinnvoll beenden kann. Die hervorragenden Programme in diesen Sprachen, die besonders optimierenden Compiler, die gewachsenen Bibliotheken, ... all dies spricht für sich, besser als jede Rangliste.
Ich fand es damals erstaunlich, dass Kommilitonen, die Informatik studierten, ihre Zeit mit Miranda, einer toten Kunstsprache und geistigen Vorbild von Haskell, verschwenden mussten. In diesem Zusammenhang sind die hier genannten sechs Programmiersprachen alle sinvoll: C, C++, Fortran, Python, Rust und Go.
Ich rate dennoch zu einer der beiden ersten, C oder C++, als erste Programmiersprache, und habe mich als Vorbereitung, einen Einstiegskurs ins Programmieren zu halten, für C++ entschieden.

😵 ) Der Aspekt, dass unter bestimmten Bedingungen eine Mischung von Sprachen sinnvoll ist, wird im Artikel Wie viele Programmiersprachen sind zu viel? (Heise Developer, 03.12.2019) beleuchtet.
So wie an entscheidender Stelle eines Kernels neben C auch mit Assembler zu programmieren erhebliche Performance bringen kann – wenn auch zu Lasten der Portierbarkeit, so dass der Assembleranteil minimal gehalten werden sollte (vgl. auch Blender in obiger Fußnote). Aber eine Aussage, mit allen Programmiersprachen ließe sich quasi alles machen (im theoretischen Sinne gleichermaßen mächtig), ist und bleibt Unsinn. Java ist für viele Anwendungsfälle eine Sackgasse, C und C++ haben bewiesen, dass sich alle möglichen Programme damit sinnvoll schreiben lassen. Hat jemand wenig Ahnung, wird Java bestimmte Probleme verhindern, will jemand etwas wirklich beherrschen, hat diese Person mit C/C++ alle Möglichkeiten. Ich würde daher die Zeit nicht auf eine Anfängersprache wie Basic oder Java verschwenden, wenn ich mich wirklich für Programmierung interessierte.
Weniger ist mehr, aber drei Sprachen zu mischen kann für ein großes Projekt lohnend sein – aber diese Sprachen haben jeweils ein klares Einsatzgebiet und werden nicht willkürlich eingesetzt – sonst wird die Software nie eine angemessene Qualität aufweisen.



Einstimmung  in C++

Historie und Normen von C++

C++ wurde 1979 von Bjarne Stroustrup (Interview 22.08.2014) bei AT&T als Erweiterung der Programmiersprache C entwickelt. Es ermöglicht ähnlich wie C eine effiziente und maschinennahe Programmierung, lässt aber auch ein hohes Abstraktionsniveau zu. Zudem ist Bjarne Stroustrup der Ansicht, dass C++ klar genug sei, um Basiskonzepte erfolgreich zu lehren sowie umfassend genug sei, um fortgeschrittene Konzepte und Techniken zu lehren.

Wie zuvor angedeutet wird C++ immer noch verändert und erweitert. Zur Historie der Standards dient die folgende Tabelle:
1985Erste Version von C++
1989Version 2.0 von C++
1998C++ Norm ISO/IEC 14882:1998: C++98
🎇Neu🎉:   Templates, STL mit Containern und Algorithmen, Strings, I/O‑Streams
2003Nachbesserung der C++ Norm ISO/IEC 14882:2003: C++03
2011Modern C++ ISO/IEC 14882:2011: C++11 [ab GCC 4.8.1 und Clang 3.3 unterstützt {aus Linux‑Magazin 12/2017}]
🎇Neu🎉:   Vereinheitlichte Initialisierung, Move‑Semantik, Typinferenz mit auto (nun kein Speicherklassen‑Specifier mehr) und decltype, Einführung der Lambda‑Funktion (auch anonyme Funktion genannt), constexpt, Übernahmen aus C11: z.B. long long (Ganzzahl mit mindestens 64 bit) oder Zusicherungen zur Übersetzungszeit static_assert, Multithreading und das Speichermodell, Reguläre Ausdrücke, Smart Pointer, Hash‑Tabellen, std::array
20141. Erweiterung von C++11 ISO/IEC 14882:2014: {anfänglich C++1y} C++14 [ab GCC 5.0 und Clang 3.4 unterstützt {aus Linux‑Magazin 12/2017}, ab GCC 6.1 Default]
🎇Neu🎉:   Verallgemeinerte Lambda‑Funktion, Verallgemeinerte constexpr‑Funktion, Erleichterte Lesbarkeit durch ' in Zahlenliteralen: 22'324'573UL, Einführung des Binärliterals: 0b0101'1110, Typdefinition bei Nutzer‑definierten Literalen: als String "123"s, Imaginärteil complex imaginaer {6.9i}; oder Zeitdauern mit h, min, s, ms, us und ns (das doppelt definierte s macht keine Probleme, da das eine auf Strings und das andere auf Zahlen operiert), auto als Rückgabetyp von Funktionen, rand() wird nicht mehr empfohlen, neues Attribut deprecated, Reader Writer Locks, Ergänzung der Standard‑Bibliothek (z.B. std::make_unique)
20172. Erweiterung von C++11 ISO/IEC 14882:2017: {anfänglich C++1z} C++17 [ab GCC 7 und Clang 5 unterstützt {aus Linux‑Magazin 12/2017}, seit GCC 9 nicht mehr experimentell, Default ab GCC 11 {siehe Phoronix, 27.06.2020}]
🎇Neu🎉:   Zur besseren Compile‑Zeit tragen bei: Fold Expressions sowie constexpr if für bedingte Übersetzung, if und switch mit Initialisieren (ähnlich wie die Laufvariable in for), Structured Binding, neuer Typ std::byte zum byte‑weisen Zugriff auf den Speicher, Entfernung von auto_ptr (C++11 std::unique_ptr ist Ersatz) und Trigraphs (drei Zeichen, die Sonderzeichen darstellen, falls diese über Tastatur nicht eingebbar sind, z.B. ??= für #), std::stringview, Parallele Algorithmen der STL, Dateisystem‑Bibliothek, neue generische Container std::any, std::optional, std::variant
20203. Erweiterung von C++11 ISO/IEC 14882:2020: {anfänglich C++2a} C++20 (vgl. Artikel auf Heise Developer von Rainer Grimm zu C++20, 28.10.2019+04.11.+11.11.+18.11.+25.11.+02.12.+09.12.+16.12.+23.12.2019 + 12.01.2020+20.01.+27.01.+02.02.+10.02.+17.02.+24.02. + 02.03.+09.03.+16.03.+23.03.+30.03.+06.04.+13.04.+20.04.+27.04. + 04.05.+11.05.+18.05.+25.05.+01.06.+08.06.+15.06.+22.06.+29.06.2020: Die vier großen Neuerungen + Überblick zur Kernsprache + Überblick zur Bibliothek + Überblick zur Concurrency + Zwei Extreme und die Rettung dank Concepts + Concepts – die Details + Concepts – die Placeholder Syntax + Concepts – Syntactic Sugar + Concepts – was wir nicht bekommen + Vordefinierte Concepts + Concepts definieren + Die Concepts Equal und Ordering definieren + Die Concepts SemiRegular und Regular definieren + Concepts in C++20: Eine Evolution oder eine Revolution? + Die Ranges‑Bibliothek + Funktionale Pattern mit der Ranges‑Bibliothek + Pythonisch mit der Ranges‑Bibliothek + Pythons range‑Funktion, die zweite + Die map‑Funktion von Python + Coroutinen – ein erster Überblick + Mehr Details zu Coroutinen + Ein unendlicher Datenstrom mit Coroutinen + Thread-Synchronisation mit Coroutinen + Coroutinen mit cppcoro + Mächtige Coroutinen mit cppcoro + Thread‑Pools mit cppcoro + Die Vorteile von Modulen + Ein einfaches math‑Modul + Module Interface Unit und Module Implementation Unit + Module strukturieren + Weitere offene Fragen zu Modulen + Der Drei‑Weg‑Vergleichsoperator <=> + Mehr Details zum Spaceship‑Operator + Optimierte Vergleiche mit dem Spaceship Operator)
🎇Neu🎉:   Module (Alternative zu Headern; Präprozessor wird unnötig), Coroutinen (zur asynchronen Programmierung), Range‑Bibliothek (um Algorithmen der STL direkt auf Container anzuwenden, die Bereichs‑Objekte erlauben Verknüpfungen mit dem Pipe‑Operator und deren Definition auf unendliche Datenströme), Concepts (eine Erweiterung der Templates), Contracts (Interfaces für Funktionen/Methoden: Vorbedingungen, Nachbedingungen und Zusicherungen während der Ausführung) {Contracts wurden für den Entwurf genehmigt, danach aber wieder gelöscht – was gut ist, kommt wieder ;}
Am 15.02.2020 wurde in Prag abgestimmt, C++20 als DIS (`Draft International Standard') zur endgültigen Zustimmung und Publikation zu schicken (vgl. reddit-Artikel). Er kann nun als abgeschlossen betrachtet werden und die Arbeit am nächsten Standard kann beginnen.
20234. Erweiterung von C++11 ISO/IEC 14882:2023: C++23
??? erste Vorschläge umfassen:   `Madular Standard Library', Bibliotheks-Unterstützung von `Coroutines', `Executors' und `Networking', auf der Sprachseite Intensivierung von `Reflection' inklusive `Introspection', Programmierung zur Compile‑Zeit und Generierung sowie `Pattern Matching' und `Contracts' ... ???

Im heutigen Sprachgebrauch bedeutet Standard C++ immer C++98 / C++03, Modern C++ bezeichnet C++11 und dessen Erweiterungen C++14 / C++17 / C++20. Was zwischen den beiden Kategorien anvisiert wurde: es sollte die Erlernbarkeit der Sprache für Anfänger verbessert werden, ebenso waren Verbesserungen im Hinblick auf die Systemprogrammierung sowie zur Erstellung von Programmbibliotheken vorrangig. Manche gehen so weit zu behaupten, in C++20 änderte sich so viel, dass es eher mit C++98 oder C++11 als mit den kleineren Erweiterungen C++14 und C++17 verglichen werden kann.
Auf jeden Fall sollte man modernes C++ lernen – Dokumente, die nicht zumindest auf dem Stand von C++14 sind, machen heutzutage (2019) keinen Sinn mehr.
Zu C++ (bzw. jeder Version von C++) gehört auch die Standardbibliothek (STL: Standard Template Library oder libstdc++, als dessen Designer gilt Alexander Stepanow – Link zu seiner Homepage mit Veröffentlichungen von Alexander A. Stepanov; vgl. Core Guidelines).
Zudem kann man auch Boost, eine freie Sammlung von C++‑Unterbibliotheken (aktuell ist 1.72 vom 11.12.2019, vgl. Artikel zu Boost 1.68), verwenden (dafür GCC den Pfad mit -L-Option angeben).
Siehe Online Quellen bzgl. weiterer Infos zu beiden Bibliotheken.
In C++17 ist z.B. die Dateisystem-Bibliothek auf neueren GCCs enthalten, so dass man dieses wertvolle Werkzeug nicht mehr über die umständlichere Einbindung der Boost-Bibliotheken erhält – die Entwicklung der Standard-Bibliotheken (libstdc++) ist also ebenso wichtig wie die des Basis‑Sprachumfangs (C++; vgl. hierzu auch LLVM/Clang, das libc++ oder GCC's libstdc++ nutzt).
Wie oben bereits erwähnt unterstützt GCC viele der C++‑Standards (eine Übersicht, welcher Compiler welche Teile der C++‑Versionen unterstützt, liefert cppreferemce): Der Default seit Version 6.1 ist C++14 mit Erweiterungen, d.h. -std=gnu++14 (C++17 wird unterstützt, gilt aber wegen des geringen Alters als experimentell – und mit Release von GCC 11 (ursprünglich was sogar von GCC 10 die Rede) in 2021 soll -std=gnu++17 gesetzt und bis zum Release von GCC 11 stabil/konform sein, vgl. Phoronix‑Bericht, 02.11.2019 bzw. 17.05.2020 und 27.06.2020 – sogar Teile des zukünftigen C++20 werden bereits unterstützt); vor GCC 6.1 war der Default C++98.
[Entsprechend ist der GCC‑Default für C bei C11 mit Erweiterungen, d.h. -std=gnu11 (hier sind die Korrekturen von C17|18 bereits enthalten; sogar Teile des zukünftigen C22 werden bereits mit -std=c2x unterstützt).]

Eigenschaften und sich daraus ergebende Haupt‑Anwendungsbereiche

C++ wird sehr viel für Bedienoberflächen (GUI; z.B. KDE; oder auch Schreibprogramme wie LibreOffice oder AbiWord) verwendet, im Grafikbereich (insb. bei Spielen, z.B. SuperTux oder SuperTuxKart, wie auch bei der Spiel-Engine Godot [ab 4.0 soll C++11 verwendet werden]), ist aber auch für numerische Berechnungen (auch wenn es dafür, anders als Fortran, nicht geschaffen wurde), Betriebssysteme (auch wenn hierfür C und Assembler geeigneter erscheinen) oder Datenbankzugriffe im Einsatz. Zudem wird auch für GCC immer mehr C++ eingesetzt, seit Konvertierungsbemühungen 08/2012 zum damaligen GCC 4.8 für GCC selbst verwendet – und aktuell berät man, auf C++11 (d.h. Modern C++; und diese frühe Version ermöglicht auch den Einsatz von GCC 5 bzw. 6; anvisiert wird der Einsatz des C++ Speicher-Modells mit atomic classes, for each loops, besserer Multi-Threading Unterstützung, etc.) umzusteigen, wohingegen LLVM gerade den Übergang zu C++14 vorbereitet (vgl. Phoronix-Artikel vom 30.09.2019).
Die Flexibilität kommt auch in Multiparadigmen zum Ausdruck: generisch, imperativ, objektorientiert, prozedural, strukturiert, funktional. Man braucht also keine Verpflichtung zu spüren, einem bestimmten Programmierstil zu folgen, auch wenn dies eigentlich nicht zur Aufgabe bzw. zu dem Problem passt. Man kann schnell Code schreiben, zu einem guten C++‑Programmierer gehört aber einiges (wer Automatismen braucht, die Performance kosten aber narrensicher sein sollen, sollte sich mit Java oder C# bescheiden – aber warum das Niveau soweit senken ...  😳 ).

Meine Wahl fiel auf C++, weil ich mir diese Sprache schon immer anschauen wollte, diese häufig in anspruchsvollen Spielen (vgl. Liste freier Computerspiele) Verwendung findet und eine gewisse Nähe zu C vorhanden ist. Wenn ich irgendwann Schülern eine Sprache näherbringen sollte, darf ich mich dafür nicht schämen müssen – und dies würde ich tun, wenn ich die bisher an Schulen vorgefundenen Wege beschreiten würde.
Also: C++ auf GNU/Linux.


Vorstellen eines Beispiel‑Programms in C++

Quelltext 

Als einfachstes C++-Programm (mit Beispielen in vielen anderen Programmiersprachen) sei auf das Hello World C++‑Beispiel hingewiesen.
Nachfolgend soll es um ein etwas komplexeres Beispielprogramm gehen:

/******************************************************************************
 * C++-Programm 'example.cpp' im Verzeichnis '~/mycpp/jmb/'                   *
 *                                                                            *
 * Funktion:    "Zerlegung einer Zahl in ihre Primfaktoren"                   *
 *                                                                            *
 * J.M.B.   [URL: https://www.jmb-edu.de/cpp_programming.html#progcppexamp]   *
 *                                                                            *
 * Lizenz:  (c) 2019  unter GPLv3: https://www.gnu.org/licenses/gpl-3.0.txt   *
 *                                                                            *
 * Erstellung:           05.08.2019                                           *
 *                                                                            *
 * Letzte Umgestaltung:  07.09.2019  (Phase 2; vgl. Phase 1 vom 08.08.2019)   *
 ******************************************************************************
 */

/* Einbau von Bibliotheken der C++-Standardbibliothek: */
# include <string>                     // fuer std::string {Container-Typ}:
                                       //   std::stoi/stoll, std::to_string
# include <vector>                     // fuer std::vector {Container-Typ}
# include <iostream>                   // fuer std::cin, std::cout, std::endl
# include <sstream>                    // fuer std::stringstream/ostringstream
# include <fstream>                    // fuer std::ofstream (Datei: file)
# include <stdexcept>                  // exception - zur Ausnahme-Behandlung
# include <cmath>                      // C-Mathe-Lib in C++ fuer: std::sqrt(l)
# include <ctime>                      // " std::time_t/struct tm/time/localtime

/* Gebrauch von ANSI-Farben in kompatiblen Terminals:
 * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 * Effekt          Code   |  Farbe   Vordergrund Hintergrund |  ASCII 27 = \033
 * zuruecksetzen     0    |  schwarz       30         40     |   = ESC-Zeichen
 * fett/hell         1    |  rot           31         41     | Z.B. hell rot
 * unterstrichen     4    |  gruen         32         42     |      auf schwarz
 * invertiert        7    |  gelb/orange   33         43     |  "\033[4;31;40m"
 * fett/hell aus    21    |  blau          34         44     | Clear Screan:
 * unterstri. aus   24    |  magenta       35         45     |"\033[2J\033[1;1H"
 * invertiert aus   27    |  cyan          36         46     | Reset: "\033[0m"
 *   * Just play ! *      |  weiss/grau    37         47     |  * Have fun! *
 */
// * Globale String-Konstanten zur Festlegung der ANSI-Steuercodes, hier
// *   bzgl. STIL_VGFARBE_HGFARBE (siehe Erklaerung oben; Esc=x1B=27=\033):
const std::string              // Definition aller ANSI-ESC-Sequenzen / Farben
// Anstelle: '# define RESET "\033[0m"' fuer Praeprozessor nun:
  RESET            = "\x1B[0m",        // Reset to Defaul: zuruecksetzen
  CLRSCR           = "\x1B[2J\x1B[1;1H", // wie in C++ 'std::system("clear");'
  UL_LWHITE_BROWN  = "\x1B[1;4;37;43m",// unterstrichen weiss auf orange
  LWHITE_BROWN     = "\x1B[1;37;43m",  // weiss auf braun/orange
  UL_LYELLOW_BROWN = "\x1B[1;4;33;43m",// unterstrichen gelb auf braun/orange
  LYELLOW_BROWN    = "\x1B[1;33;43m",  // gelb auf braun/orange
  UL_LRED_BLACK    = "\x1B[1;4;31;40m",// unterstrichen rosa auf schwarz
  LRED_BLACK       = "\x1B[1;31;40m",  // rosa auf schwarz
  LYELLOW_BLACK    = "\x1B[1;33;40m",  // gelb auf schwarz
  UL_LYELLOW_GREEN = "\x1B[1;4;33;42m",// unterstrichen gelb auf gruen
  RED_BROWN        = "\x1B[31;43m";    // rot auf braun/orange

// * Globale Variable zur Festlegung, ob Ausgabe ohne ANSI-Farben gewuenscht ist
bool farbLos = false;                  // wird nur von main umgeaendert!

// * Nun eine zweiteilige Funktion zum (ggf. farbigen) Start bzw. Ende ;)
void begruessung (std::ostream& os, bool anfang, std::string prgname)
{ using std::endl;                     // endl bedeutet nun std::endl
  if (anfang)                          // * anfang: 1 = true (= Begruessung)
  { /* Begruessung */
    if (farbLos)
    {                                  // prgname = argv[0], d.h. Aufrufstring
      os << " * Programm '" << prgname << "' zur Ausgabe der Primfaktoren *";
      os << endl;
    } else
    {                                  // prgname = argv[0], d.h. Aufrufstring
      os << UL_LYELLOW_BROWN << " * Programm '" << UL_LYELLOW_GREEN << prgname;
      os << UL_LYELLOW_BROWN << "' zur Ausgabe der Primfaktoren *" << RESET;
      os << endl;
    }
  } else                               // * anfang: 0 = false (= Abschied)
  { /* Verabschiedung */
    if (farbLos)
    {
    os << " * Macht's gut, Kinners!  \U0001F609 * -<\u00A9 2019 J.M.B., GPLv3\U0001F60E>-";
    os << endl;
    } else
    {
    os << UL_LWHITE_BROWN << " * Macht's gut, Kinners! ";
    os << LYELLOW_BROWN << "\U0001F609" << UL_LWHITE_BROWN;
    os << " * -<\u00A9 2019 J.M.B., GPLv3" << RED_BROWN << "\U0001F60D";
    os << UL_LWHITE_BROWN << ">-" << RESET << endl;
    }
  }
} // Ende: void begruessung (std::ostream& os, bool anfang, std::string prgname)

// * Nun eine Funktion zur Ausgabe von Fehler-Hinweisen:
void meldeInfo (std::ostream& os, bool errMsg, std::string msgStr)
{ using std::endl;                     // endl bedeutet nun std::endl
  if (errMsg)                          // errMsg = 1: "Fehler: ..."
  { /* ehemals void fehlerInfo (std::ostream& os, std::string fehlerStr) */
    if (farbLos)
    {
    os << " * Fehler: " << msgStr << "!" << endl;
    } else
    {
    os << LRED_BLACK << " * " << UL_LRED_BLACK << "Fehler:" << RESET;
    os << " " << msgStr << "!" << endl;
    }
  } else                               // errMsg = 0: "Info: ..."
  { /* ehemals void hinweisInfo (std::ostream& os, std::string hinweisStr) */
    if (farbLos)
    {
    os << " * Info: " << msgStr << endl;
    } else
    {
    os << LYELLOW_BROWN << " * " << UL_LYELLOW_BROWN << "Info:" << RESET;
    os << " " << msgStr << endl;
    }
  }
} // Ende: void meldeInfo (std::ostream& os, bool errMsg, std::string msgStr)

void ausgabeInfotext (std::ostream& os, bool hlpMsg)
{
  if (hlpMsg)                          // hlpMsg = 1: Hilfs-Text ..."
  {
    if (!(farbLos))
     os << LYELLOW_BROWN << " * Hilfstext:" << LWHITE_BROWN << "\n";
    os << " *******************************************************************\n";
    os << " * Generelle Funktion und Spezialparameter des Programms 'example' *\n";
    os << " *-===============================================================-*\n";
    os << " * Es ist moeglich, als 1. Parameter ein Steuerflag einzugeben:    *\n";
    os << " * o Keinen Spezialparameter (d.h. kein Steuerflag):               *\n";
    os << " *   Parameter / interaktiv einzugebende Zahlen als Strings holen  *\n";
    os << " *   und nacheinander bearbeiten: jeweils Ganzzahlen zwischen      *\n";
    os << " *   2 und 18446744073709551615 (2^64-1), auch hex. wie 0x9AFE,    *\n";
    os << " *   in Primfaktoren zerlegen und die Faktoren ausgeben.           *\n";
    os << " *   Alle Nichtzahlen werden ignoriert - sie koennen aber als      *\n";
    os << " *   Zahlentrenner eingesetzt werden (wie 66,0x1B bzw. 66/12/9).   *\n";
    os << " *   Eingabe eines Zeichens: 'H' - Hilfs-, 'V'- Versions-Text und  *\n";
    os << " *   'B' - beide interaktiv abrufbar, mit '1'/'Q' abbrechen.       *\n";
    os << " * o -f --file    => Ausgabe des Parameter-Teils in eine Datei -   *\n";
    os << " *                   eine interaktive Abfrage erfolgt nicht.       *\n";
    os << " * o -m --mono    => Monochromatisch mit Default-Farben vom xterm  *\n";
    os << " * o -i --inverse => Invertierte Darstellung (mit Nostalgie \U0001F917 )   *\n";
    os << " *                   gelb auf schwarz (wie Turbo Pascal 3.0)       *\n";
    os << " * o -h --help    => Diesen Hilfstext ausgeben und beenden         *\n";
    os << " * o -v --version => Version, Datum, Autorinfo, Lizenz & beenden   *\n";
    os << " * o -b -vh -hv   => Versions- und Hilfs-Text ausgeben & beenden   *\n";
    os << " *******************************************************************";
  } else                               // hlpMsg = 1: Versions-Text ..."
  {
    if (!(farbLos))
     os << LYELLOW_BROWN << " * Versionstext:" << LWHITE_BROWN << "\n";
    os << " *******************************************************************\n";
    os << " * C++-Programm fuer GNU/Linux >example< in Version 2.00 (Phase 2) *\n";
    os << " * \u00A9 2019  J.M.B. (URL: https://www.jmb-edu.de/)  03.09.2019 GPLv3 *\n";
    os << " * [URL: https://www.jmb-edu.de/cpp_programming.html#progcppexamp] *\n";
    os << " *******************************************************************";
  }
  if (!(farbLos))
    os << RESET;
  os << std::endl;
} // Ende: void ausgabeInfotext (std::ostream& os)

// * Globale Variable fuer Dateiname
std::string outputFilename = "";  // v- spaeter mit Zeitstempel YYYYMMDDHHMMSS..
std::string myOutputFilename = "PrimZahlZerlegung_JMB.txt";// Ueberschreibschutz

// * Zwei Funktionen zum Erhalt des Datumsstrings (braucht '# include <ctime>'):
std::string addZeroIOD (std::ostream& os, std::string addStr)
{
  if (addStr.length() == 1)
  {
    addStr = std::string("0")+addStr;  // ergaenze fuehrende 0 bei einer Ziffer
  } else if ((addStr.length() == 0) || (addStr.length() > 2))  // 2 ist OK :)
  {
    meldeInfo (os, 1, "Falsche Stringlaenge in addZeroIOD!");
    return "";                         // macht nichts kaputt - Fehler sichtbar
  }
  return addStr;
} // Ende: addZeroIOD (std::ostream& os, std::string addStr)

std::string getDatStr (std::ostream& os)
{ using namespace std;                 // sollte vermieden werden; hier klein
  tm *nun;                             // ein struct fuer Zeitangaben: tm_*
  time_t now = time(0);                // Sekunden seit 00:00, Jan 1 1970 UTC
  nun = localtime(&now);               // Konvertieren in Kalenderzeit
  int dateYear = 1900 + nun->tm_year;  // Jahr:     seit 1900 (= 0)
  int dateMonth =   1 + nun->tm_mon;   // Monate:   0-11
  int dateDay =         nun->tm_mday;  // Tage:     1-31
  int timeHour =        nun->tm_hour;  // Stunden:  0-23
  int timeMin =         nun->tm_min;   // Minuten:  0-59
  int timeSec =         nun->tm_sec;   // Sekunden: 0-60 (Schaltsekunde)
  string dateTime = to_string (dateYear);  // Start dateTime mit Jahreszahl
  string addStr = addZeroIOD (os, to_string (dateMonth)); // Monat zweist. ..
  dateTime += addStr;                  // .. in addStr und diesen anfuegen
  addStr = addZeroIOD(os, to_string (dateDay));      // Tag zweistellig ..
  dateTime += addStr;                  // .. in addStr und diesen anfuegen
  addStr = addZeroIOD(os, to_string (timeHour));     // Stunden zweistellig ..
  dateTime += addStr;                  // .. in addStr und diesen anfuegen
  addStr = addZeroIOD(os, to_string (timeMin));      // Minuten zweistellig ..
  dateTime += addStr;                  // .. in addStr und diesen anfuegen
  addStr = addZeroIOD(os, to_string (timeSec));      // Sekunden zweistellig ..
  dateTime += addStr;                  // .. in addStr und diesen anfuegen
  return dateTime;                     // Zeitstring als YYYYMMDDHHMMSS zurueck
} // Ende: std::string getDatStr (std::ostream& os)

// * Globale Variable fuer Primfaktorliste:
// *   Funktion suchePrimFaktoren bestueckt sie,
// *   Funktion ausgabePrimFaktoren liest sie
// *     fuer Ausgabe aus und setzt sie zurueck.
std::vector<unsigned long long> wertepfs;

// * Globale Variable, die bei ausgabePrimFaktoren ausgelesen und
// *   ausgegeben wird und an deren Ende auch inkrementiert (+1) wird.
unsigned long long anzFaktorZerlegungen = 1;

// * Globale Variablen fuer Eingabe ueber Parameter/Tastatur ...
std::string inputStr = "";             // Einlese-String
std::string::size_type sz = 0;         // Entspricht size_t (ein Alias)
// sz dient als Marker fuer StringRest-Kopie in verwandleStringInULL !

// * Funktion zur PF-Ermittlung - der eigentliche Algorithmus (d.h. das Herz)
void suchePrimFaktoren (unsigned long long n)
{
  /* 1. Schritt: */
  // Anzahl 2-en ausgeben, durch die n (vor 1. Schritt) dividiert werden kann
  while (n%2 == 0)                     // wenn bei Division kein Rest
  {
    wertepfs.push_back (2);            // jeder Faktor wird gemerkt -> wertepfs
    n = n/2;
  }
  /* 2. Schritt: */
  // n (Anfang 2. Schritt) muss nun ungerade sein - Primfaktoren 2 wurden
  // entdeckt, somit kann man immer eine Zahl uebergehen, daher i=i+2
  for (unsigned long long i = 3; i <= std::sqrt(n); i = i+2)
  { // .. nach GCC Namspace-Fix sqrtl statt^^^^ verwenden (C++17-Voraussetzung)
    // Solange n durch i teilbar ist, wird i ausgegeben (analog 2 oben)
    while (n%i == 0)                   // wenn bei Division kein Rest
    {
      wertepfs.push_back (i);          // jeder Faktor wird gemerkt -> wertepfs
      n = n/i;
    }
  }
  /* 3. Schritt: */
  // Wenn for bis i = Wurzel n (n nach 1. Schritt) nicht n=1 (Ende 2. Schritt)
  // liefert, muss n der letzte fehlende Primfaktor der Zerlegung sein
  if (n > 2)                           // n ist der letzte fehlende PF ...
  {
    wertepfs.push_back (n);            // n wird als PF gemerkt -> wertepfs
  }
} // Ende: void suchePrimFaktoren (unsigned long long n)

void ausgabePrimFaktoren (std::ostream& os, unsigned long long wert)
{
  int pfanz = 0;                       // Anzahl Primfaktoren - hier int OK!
  std::string pfliststring = "  ";     // Def.+Initialisierung von String
  std::string dummystring = "";
  unsigned int curoutstrllength = 0;   // Laenge der aktuellen pfliststring-Z.
  unsigned int addfutstrlength= 0;     // Laenge der naechsten Ergaenzung
  const unsigned int maxlength = 75;   // Angabe der maximalen Zeilenlaenge - 1
  std::string zeilUmbrStr = ",\n *   ";
  if (!(farbLos))
    zeilUmbrStr = ",\n"+LYELLOW_BROWN+" * "+RESET+"  ";
  for (unsigned long long primfaktor : wertepfs)
  {
    if (pfanz < 1)
    {
      pfliststring += std::to_string (primfaktor);  // to_string ab C++11
      curoutstrllength = pfliststring.length();     // Startlaenge der Zeile
    } else                                          // es gab Vorgaenger: +' ,'
    {
      dummystring = std::to_string (primfaktor);
      addfutstrlength = (2+dummystring.length());
      if (!(curoutstrllength+addfutstrlength+2 < maxlength))
      {
        pfliststring += zeilUmbrStr;                // Zeilenumbruch + Platz
        curoutstrllength = addfutstrlength;         // Neuer Start Zeilenlaenge
      } else
      {
        pfliststring += ", ";                       // Komma + wenig Platz
        curoutstrllength += addfutstrlength;        // Zeilenlaenge vergroess.
      }
      pfliststring += std::to_string (primfaktor);  // aktuelle PF dem Str. zuf.
    }
    ++pfanz;
  }
  pfliststring += ".";                 // Abschlusspunkt
  if (pfanz < 2)
  {
    if (!(farbLos))
    {
      os << LYELLOW_BROWN << " * " << RESET;
    } else
    {
     os << " * ";
    }
    os << "Der " << anzFaktorZerlegungen << ". Wert '" << wert;
    os << "' ist bereits prim." << std::endl;   // \n und Flushen am Schluss
  } else
  {
    if (!(farbLos))
    {
      os << LYELLOW_BROWN << " * " << RESET;
    } else
    {
      os << " * ";
    }
    os << " Der " << anzFaktorZerlegungen << ". Wert '" << wert;
    os << "' konnte in " << pfanz << " Primfaktoren zerlegt werden:\n";
    if (!(farbLos))
    {
      os << LYELLOW_BROWN << " * " << RESET;
    } else
    {
      os << " * ";
    }
    os << pfliststring << std::endl;   // Liste als Abschluss, \n und Flushen!
  } // Angaben x86_64-Linux, signed|uns. int|long|long long ist systemabhaengig:
  if        (wert == 18446744073709551615ull)// 2^64-1 groesster 64 Bit - Wert!
  {
    meldeInfo (os, 0, "Ende von 'unsigned long long' erreicht - groesser nur mit Spezial-Libs!");
  } else if (wert >  9223372036854775807)  // 2^63-1 grosster signed 64 Bit - W!
  {
    meldeInfo (os, 0, "Hier war 'unsigned long long' statt '>long long<' notwendig!");
  } else if (wert >           4294967295)  // 2^32-1 grosster unsigned 32 Bit-W!
  {
    meldeInfo (os, 0, "Hier war 'unsigned long long' statt '>unsigned int<' notwendig!");
  } else if (wert >           2147483647)  // 2^31-1 grosster signed 32 Bit-W!
  {
    meldeInfo (os, 0, "Hier war 'unsigned long long' statt '>int<' notwendig!");
  } else if (wert >                  255)  // 2^8-1  grosster unsigned 8 Bit-W!
  {
    meldeInfo (os, 0, "Hier war 'unsigned long long' statt '>unsigned char<' notwendig!");
  } else if (wert >                  127)  // 2^7-1  grosster signed 8 Bit-W!
  {
    meldeInfo (os, 0, "Hier war 'unsigned long long' statt '>signed char<' notwendig!");
  }
  /* Vector wertepfs loeschen, damit Platz fuer neue Eintraege ist */
  wertepfs.clear();
  ++anzFaktorZerlegungen;              // Globale Variable als Zaehler: +1
} // Ende: void ausgabePrimFaktoren (std::ostream& os, unsigned long long wert)

// * Diese Funktion loescht fuehrende Zahlen (zu gross fuer ULL), indem diese
// *   im Uebergabestring geloescht werden und der Rest uebergeben wird.
// * Fuer vorzeichenlose Ganzzahl-Typen ist die Funktion schon allgemein.
std::string loescheFuehrendeZahlen (std::ostream& os, std::string stringRest)
{
  unsigned int stringRestLength = stringRest.length();  // Laenge stringRest
  if (stringRestLength == 0)
  { // die Funktion kann mit Leerstring starten und damit enden - kein Fehler!
    meldeInfo (os, 1, "Leerstring bei loescheFuehrendeZahlen sollte nicht vorkommen");
    return "";
  }
  if (!(std::isdigit(stringRest[0])))
  {
    meldeInfo (os, 1, "Aufruf von loescheFuehrendeZahlen mit 1. Zeichen keine Zahl");
    return stringRest;                 // Parameter startet mit Zahl - nix tun
  } else
  {                                    // 1. Zeichen [0] wird geloescht - mehr?
    for (unsigned int i = 1; i < stringRestLength; ++i)
    {                                  // wieviele Zeichen werden geloescht: i
      if (!(std::isdigit(stringRest[i])))  // keine Zahl, dann hier Abbruch
      {
        return (stringRest.erase(0, i)); // ab 0 {= Anfang}: i Zeichen loeschen
      }
    }
    return "";                           // alle Zeichen Zahlen - Leerstring
  }
} // Ende: string loescheFuehrendeZahlen (ostream& os, std::string stringRest)

// * Diese Funktion loescht fuehrende Nicht-Zahlen, indem diese im
// *   Uebergabestring geloescht werden und der Rest uebergeben wird.
// * Fuer vorzeichenlose Ganzzahl-Typen ist die Funktion schon allgemein,
// *   wenn man nicht strenger mit dem Anwender sein will/muss.  ;)
std::string loescheFuehrendeNichtZahlen (std::ostream& os, std::string stringRest)
{
  unsigned int stringRestLength = stringRest.length();  // Laenge stringRest
  if (stringRestLength == 0)
  { // die Funktion kann mit Leerstring starten und damit enden - kein Fehler!
    return "";
  }
  if (std::isdigit(stringRest[0]))
  {
    return stringRest;                 // Parameter startet mit Zahl - nix tun
  } else
  {                                    // 1. Zeichen [0] wird geloescht - mehr?
    if ((stringRest[0] == '-') && (std::isdigit(stringRest[1])))
      meldeInfo (os, 1, "Negative Zahlen sind nicht erlaubt: '-' wird verworfen");
    for (unsigned int i = 1; i < stringRestLength; ++i)
    {                                  // wieviele Zeichen werden geloescht: i
      if (std::isdigit(stringRest[i]))
      {
        return (stringRest.erase(0, i)); // ab 0 {= Anfang}: i Zeichen loeschen
      } else if ((stringRest[i] == '-') && (std::isdigit(stringRest[(i+1)])))
      {                                  // nur bei - Meldung - Rest stumm weg
        meldeInfo (os, 1, "Negative Zahlen sind nicht erlaubt: '-' wird verworfen");
      }                                  // alles andere wird uebergangen
    }
    return "";                           // alle Zeichen keine Zahlen - Leerstr.
  }
} // Ende: string loescheFuehrendeNichtZahlen (ostream& os, string stringRest)

// * Diese Funktion wandelt einen String in die zugehoerige ULL
// *   (groesster positiver C++-Standard-Ganzzahltyp) und faengt
// *   alle Probleme ab - da negative Werte ohne jede Kennzeichnung
// *   unsinnig konvertiert werden, wird zur Eliminierung von '-'
// *   die obige Funktion loescheFuehrendeNichtZahlen vorgeschaltet.
// * Sollte diese Funktion in anderem Zusammenhang verwendet werden,
// *   darf nicht mehr 0 angemahnt und ansonsten zum Weitermachen verwendet
// *   werden, sondern sollte ueber einen Fehlerstatus weitergegeben werden,
// *   der ggf. eine globale Variable ist als ullConvErr oder ULL global und
// *   den Boolean als Rueckgabe, da eine Funktion nur einen Rueckgabewert
// *   hat (wenn man kein Konstrukt uebergeben will) - zudem sollte als
// *   Parameter einfach "(std::string teilString)" mitgegeben werden - hier
// *   werden globale Variablen wegen komplexer KBD-Schleife verwendet ... -
// *   und der Reststring wird in dieser Funktion mit sz beschnittet ... -
// *   ansonsten ist die Funktion bereits allgemein.
unsigned long long verwandleStringInULL (std::ostream& os)
{
  unsigned long long wert = 0;         // Definition mit Startwert: Rueckgabe
  try                                  // Solange kein Fehlert auftritt ...
  {
    wert = std::stoull (inputStr,&sz,0);  // ... sollte dies die Zahl sein.
    if (wert == 0)                     // 0 wird sonst verwendet als 'continue'
    {
      meldeInfo (os, 1, "Es wurde '0' eingegeben"); // 0 bei Faktorzerlegung unsinnig
    }
  } catch (std::invalid_argument &exc) // --- Erster Handler ...
  {
    meldeInfo (os, 1, "Ungueltiger Aufrufparameter: nicht in ULL konvertierbar");
    inputStr = loescheFuehrendeZahlen(os, inputStr);
    return 0;                          // hier abbrechen, aussen weitermachen
  } catch (std::out_of_range &exc)     // --- zweiter Handler ...
  { // bei ULL bedeutet ^- dies zu gross - negativ wird nicht erkannt!
    meldeInfo (os, 1, "Ungueltiger Aufrufparameter: ausserhalb der ULL-Grenzen (zu gross)");
    inputStr = loescheFuehrendeZahlen(os, inputStr);
    return 0;                          // hier abbrechen, aussen weitermachen
  } catch( ... )                       // ... dritter Handler fuer den Rest.
  {
    meldeInfo (os, 1, "Seltsames Problem bei der Umwandlung in ULL-Grenzen aufgetreten");
    inputStr = loescheFuehrendeZahlen(os, inputStr);
    return 0;                          // hier abbrechen, aussen weitermachen
  }
  // Nur Loeschen der Zahl bei Ueberlauf/Umwandlung verlaesst zuvor - return 0:
  inputStr = inputStr.substr(sz);      // inputStr ist nun der Rest ...
  return wert;
} // Ende: unsigned long long verwandleStringInULL (std::ostream& os)

// * Nun zwei Funktionen, zuerst werden Kommandozeilen-Parameter gesucht (CLP),
// * dann wird eine Eingabe ueber die Tastatur erbeten und gelesen (KBD).
// * Wegen der zentralen Schleifen stehen sie direkt vor main ...

// Diese Funktion ermittelt die Parameter und wertet diese einzeln als String
// aus, die in jeweils eine oder mehrere Zahlen ueberfuehrt werden,
// die in einer Schleife abgearbeitet werden:
// sie sollen in Primfaktoren zerlegt und so ausgegeben werden.
unsigned long long eingabeCLPZahlZumFaktorisieren (std::ostream& os, int argc,
  const char* argv[], bool frstParamUsed)
{
  unsigned long long wert = 0;         // < 2 ist Fehlercode, > 1 ist ges. Wert
    for (int i = 1; i < argc; ++i)
    {
      if ((i == 1) && (frstParamUsed))    // 1. Parameter "-f" wegwerfen!
      {
        meldeInfo (os, 0, "1. Parameter enthielt Steuerflag - ist kein Input.");
        continue;
      }
      inputStr = argv[i];  // fuer meine Funktionen editierbar als std::string
      // * String muss mit Zahl beginnen, kein '-' oder gar Buchstabe:
      inputStr = loescheFuehrendeNichtZahlen (os, inputStr);
      while (!inputStr.empty())
      {
        wert = verwandleStringInULL (os);  // inputStr wird verwendet
// * Bedeutet: std::stoull (dummyString,&sz,0) & inputStr.substr(0,sz) kopiert.
        if (wert > 1)                    // OK, wert kann zerlegt werden
        {
          /* PFs ermitteln und in vector wertepfs speichern */
          suchePrimFaktoren (wert);
          /* PFs aus wertepfs lesen, mit "," getrennt ausgeben & "." am Ende */
          ausgabePrimFaktoren (os, wert);
        } else if (wert == 1)          // wegen Benchmark auch hier Abbruch
        {
          meldeInfo (os, 0, "Abbruch des Programms wurde gewuenscht! - Benchmark?");
          return 0;                    // Programmabbruch mit OK-Code gewuenscht
        } // = 0 wird nicht behandelt, da verwandleStringInULL Fehler meldet
        // * String muss mit Zahl beginnen, kein '-' oder gar Buchstabe:
        inputStr = loescheFuehrendeNichtZahlen (os, inputStr);
      }
    }
  if (wert == 0)                       // wenn nicht ueber return: continue
    wert = 5;                          // uninteressanter wird - unbeachtet
  return wert;                         // Weitergabe der Zahl ...
} // Ende: unsigned long long eingabeCLPZahlZumFaktorisieren (std::ostream& os,
//   \       int argc, const char* argv[], bool frstParamUsed)

// Diese Funktion liest bei Bedarf einen Sting, der in eine oder mehrere
// Zahlen ueberfuehrt wird, die in einer Schleife abgearbeitet werden:
// sie sollen in Primfaktoren zerlegt und so ausgegeben werden.
unsigned long long eingabeKBDZahlZumFaktorisieren (std::ostream& os)
{
  unsigned long long wert = 0;         // < 1 ist falsch - d.h. wiederholen,
                                       // 1 ist Fehlercode zum Programmabbruch,
                                       // > 1 ist gesuchter Wert!
  while (wert < 1)                     // Bis wert mindestens 1 ist einlesen
  {
    inputStr = "";
    // * Eingabe-Aufforderung:
    meldeInfo (os, 0, "Spezial:  'H': Hilfe \U0001F198-'B'-'V': Versionsinfo \U0001F6C8 , '1'/'Q': Abbruch \u274C.");
    meldeInfo (os, 0, "Gib ansonsten eine ganze Zahl \u2115 in [2;18446744073709551615]\u2705 ein:");
    if (farbLos)
    {
      os << " * ";
    } else
    {
      os << LYELLOW_BROWN << " * " << RESET;
    }
    os << "      \U0001F644          \U0001F635";
    os << "          \U0001F634         \U0001F612 ";
    // * Lese von Tastatur in String:
    std::getline(std::cin, inputStr);
    if (inputStr.length() == 1)
    {
      switch (inputStr[0])
      {
        case 'H': case 'h':
        { // * Hilfsinfo und Abbruch der Funktion mit '5' {= continue}
          ausgabeInfotext (os, 1);
          return 5;
        }
        case 'V': case 'v':
        { // * Versionsinfo und Abbruch der Funktion mit '5' {= continue}
          ausgabeInfotext (os, 0);
          return 5;
        }
        case 'B': case 'b':
        { // * Versionsinfo und Abbruch der Funktion mit '5' {= continue}
          ausgabeInfotext (os, 0);
          ausgabeInfotext (os, 1);
          return 5;
        }
        case '1': case 'Q': case 'q':
        { // * Programm-Abbruch: '1' - break kann (wie zuvor: '5') entfallen.
          return 0;
        } // ein Zweig: 'default : ...' als Joker ist nicht noetig.
      }
    }
    // * String muss mit Zahl beginnen, kein '-' oder gar Buchstabe:
    inputStr = loescheFuehrendeNichtZahlen (os, inputStr);
    while (!inputStr.empty())
    {
      wert = verwandleStringInULL (os);  // inputStr wird verwendet
// * Bedeutet: std::stoull (dummyString,&sz,0) & inputStr.substr(0,sz) kopiert.
      if (wert > 1)                    // OK, wert kann zerlegt werden
      {
        /* PFs ermitteln und in vector wertepfs speichern */
        suchePrimFaktoren (wert);
        /* PFs aus wertepfs lesen, mit "," getrennt ausgeben und "." am Ende */
        ausgabePrimFaktoren (os, wert);
      } else if (wert == 1)
      {
        meldeInfo (os, 0, "Abbruch des Programms wurde gewuenscht!");
        break;
      } // = 0 wird nicht behandelt, da verwandleStringInULL Fehler meldet
      // * String muss mit Zahl beginnen, kein '-' oder gar Buchstabe:
      inputStr = loescheFuehrendeNichtZahlen (os, inputStr);
    }
  }
  if (wert == 1)
  {
    wert = 0;
  }
  return wert;                         // Weitergabe der Zahl zur Faktorisierung
} // Ende: unsigned long long eingabeKBDZahlZumFaktorisieren (std::ostream& os)

// * Die folgende Funktion enthaelt den Grossteil des ehemaligen Kerns
// * von main, der wegen Uebergabe von ostream nun besser als Funktion
// * aufgerufen werden kann.
// * Es wird die Hauptfunktion als CLP = Parameter-Abfrage und als Schleife
// * KBD als interaktive Tastaturabfrage behandelt und Return-Codes ermittelt.
int abfrageSchleifePFBestimmungAusgabe (std::ostream& os, const int argc,
  const char* argv[], bool frstParamUsed)
{
  /* Zahl aus Kommandozeilen-Parameter ermitteln,
     die im Anschluss in PFs aufgespalten werden soll */
  unsigned long long wert = eingabeCLPZahlZumFaktorisieren (os, argc, argv, frstParamUsed);
  // * Variablen-Deklaration von wert als Rueckgabewert der beiden
  // * Eingabefunktionen; in abfrageSchleifePFBestimmungAusgabe bedeutet:
  // *   '> 1': continue (Programm laeuft weiter - Tastatureingabe-Schleife),
  // *  '== 1': Abbruch mit Fehlercode 1 und
  // *  '== 0': Abbruch gewuenscht, d.h. mit Code 0 fuer alles OK!
  // CLP-Einlesen nun beendet ... noch die letzten Auswertungen von wert
  if (outputFilename == myOutputFilename)
  { // * Datei-Ausgabe hat keinen KBD-Teil
    if (wert > 1)                      // Keine Abbruchbedingung bislang,
      wert = 0;                        //   also regulaeres Ende
    meldeInfo (os, 0, "Hier noch zum Abschluss der Versions- und Hilfs-Text des Programms:");
    ausgabeInfotext (os, 0);           // Versions-Text ausgeben
    ausgabeInfotext (os, 1);           // Hilfs-Text ausgeben
    return wert;                       // Verabschiedung erfolgt im main-Zweig
  }
  if (wert == 0)                       // {(wert < 2): return (wert) ginge auch}
  { // * Abbruch nach CLP gewuenscht (kein KBD) - Benchmark
    return 0;                          // dann Abbruch mit Fehlercode 0
  } else if (wert == 1)
  {                                    // Wesentliches Problem
    return 1;                          // dann Abbruch mit Fehlercode 1
  }                                    // ausser 0 und 1 bedeutet alles continue
  do                                   // Schleife bzgl. Tastatureingabe ...
  {
    /* Zahl aus Tastatureingabe (KBD; von cin) ermitteln,
       die im Anschluss in PFs aufgespalten werden soll */
    wert = eingabeKBDZahlZumFaktorisieren (os);
    if (wert == 1)                   // Wesentliches Problem
      return 1;                      // dann Abbruch mit Fehlercode 1
    if (wert == 0)                   // Abbruch durch Eingabe 1 gewaehlt
      return 0;                      // -> Abbruch ohne Fehlercode (0 = OK)
// * Zur Erinnerung, main verabschiedet (ausser Dateiausgabe) und reicht
// *   am Ende den Return-Code an das Betriebssystem/xterm/Shell weiter ...
  } while (wert > 1);                // gleichbedeutend mit !(wert == 1),
                                     // und dies sollte oben abbrechen ...
  // *** HIER SOLLTE NICHTS LANDEN - EINFACH DURCHSCHAUEN ...
  meldeInfo (os, 1, "Hier sollte - am Ende von abfrageSchleifePFBestimmungAusgabe - nichts landen");
  return 1;                            // Uebergabewert (= Fehlercode) an main
} // Ende: int abfrageSchleifePFBestimmungAusgabe (ostream& os, const int argc,
//   \       const char* argv[], bool frstParamUsed)

// * Hauptfunktion - nimmt Parameter entgegen und ermittelt ggf. einen
// * Steuer-Parameter, aendert ggf. die globale Variable farbLos und setzt
// * ggf. die an CLP durchgereichte Variable frstParamUsed (beides passiert
// * nur hier), gibt dann viele Parameter wie genauer outputstream und
// * Parameter an die Steuerfunktion abfrageSchleifePFBestimmungAusgabe
// * weiter, die die eigentliche Programmfunktionalitaet ausloest und
// * die Return-Codes liefert.
// * Im wesentlichen kuemmert sich main um die Aenderung von ostream fuer
// * unterschiedliche Ausgaben: neben cout (7. Moeglichkeit, und mit
// * Farbaenderungen auch 2.+3. Moeglichkeit) und Datei (1. Moeglichkeit)
// * waere auch in einen String denkbar, was hier eher stoeren wuerde,
// * zu Testzwecken aber praktisch ist ...:
// *     ``std::ostringstream oss{};'', so dass dieser abgfragt werden kann:
// *     ``if (oss.str() == "...") { ... }''.
int main (int argc, const char* argv[])
{                                      // Hauptfunktion mit Aufruf-Parametern
  try                                  // Absichern gegenueber Exceptions ...
  {
    std::string prgname = argv[0];     // Aufrufname des Programms
    std::string frstParamStr = "";
    bool frstParamUsed = 0;            // neue bool an CLP: 1. Param. continue
    int retStat = 0;                   // Fehlercode an OS/xterm/shell
    // nach Parameter fragen bzgl. output - bool fileOut setzen!
    if (argc > 1)                      // mindestens ein Parameter gegeben ...
      frstParamStr = argv[1];          // String hat 1. Parameter oder ist leer
    if ((frstParamStr == "-f") || (frstParamStr == "--file"))
    { // * 1. Moeglichkeit: Ausgabe in Datei
      frstParamUsed = 1;               // 1. Parameter wurde verwendet
      /* Begruessung */                // Erst auf den Bildschirm ..
      begruessung (std::cout, 1, prgname); // .. Begruessung
      myOutputFilename = std::string("PrimZahlZerlegung_")+
                         getDatStr(std::cout)+std::string("_JMB.txt");
      outputFilename = myOutputFilename;
      meldeInfo (std::cout, 0, "Die wirkliche Ausgabe erfolgt nun ueber die Datei:");
      meldeInfo (std::cout, 0, std::string(" * '")+outputFilename+std::string("'!"));
      farbLos = true;                  // keine ANSI-Farben
      std::ofstream datei {outputFilename};
      // Datei outputFilename -^ wird neu erzeugt oder bei Existenz
      // ueberschrieben (daher ggf. sinnvoller mit Zeitstempel im Namen);
      // dass Oeffnen und Schliessen ueberlaesst man sicherheitshalber
      // den Mechanismen von ofstream (Konstruktor und Destruktor) - AWG!
      /* Begruessung */                // .. nun auch in die Datei
      begruessung (datei, 1, prgname);
      meldeInfo (datei, 0, std::string("Diese Ausgabe-Datei heisst: '")+outputFilename+std::string("'!"));
      /* Hauptteil */
      retStat = abfrageSchleifePFBestimmungAusgabe (datei, argc, argv, frstParamUsed);
      if (anzFaktorZerlegungen == 1)
      { // Info, wenn Primfaktoren ueberhaupt berechnet werden sollten
        meldeInfo (datei, 1, "Es konnte keine sinnvolle Eingabe gefunden werden");
      }
      /* Verabschiedung */             // erst in die Datei ..
      begruessung (datei, 0, prgname); // Verabschiedung
      farbLos = false;                 // nun wieder mit ANSI-Farben
      if (anzFaktorZerlegungen == 1)
      { // Info, wenn Primfaktoren ueberhaupt berechnet werden sollten
        meldeInfo (std::cout, 1, "Es konnte keine sinnvolle Eingabe gefunden werden");
      }
      /* Verabschiedung */             // nun auch auf den Bildschirm ..
      begruessung (std::cout, 0, prgname); // Verabschiedung
    } else if ((frstParamStr == "-m") || (frstParamStr == "--mono"))
    { // * 2. Moeglichkeit: monochromatische Darstellung - Default von xterm
      frstParamUsed = 1;               // 1. Parameter wurde verwendet
      farbLos = true;                  // keine ANSI-Farben
      /* Begruessung */                // Auf den Bildschirm ..
      begruessung (std::cout, 1, prgname); // Begruessung monochrom
      meldeInfo (std::cout, 0, "Die wirkliche Ausgabe erfolgt nun monochromatisch:");
      /* Hauptteil */
      retStat = abfrageSchleifePFBestimmungAusgabe (std::cout, argc, argv, frstParamUsed);
      if (anzFaktorZerlegungen == 1)
      { // Info, wenn Primfaktoren ueberhaupt berechnet werden sollten
        meldeInfo (std::cout, 1, "Es konnte keine sinnvolle Eingabe gefunden werden");
      }
      /* Verabschiedung */             // Auf den Bildschirm ..
      begruessung (std::cout, 0, prgname); // Abschied monochrom
    } else if ((frstParamStr == "-i") || (frstParamStr == "--inverse"))
    { // * 3. Moeglichkeit: inverse Darstellung (gelb auf schwarz - Tur.Pascal3)
      frstParamUsed = 1;               // 1. Parameter wurde verwendet
      farbLos = true;                  // keine ANSI-Farben
      std::cout  << LYELLOW_BLACK << "\n";
      /* Begruessung */                // Auf den Bildschirm ..
      begruessung (std::cout, 1, prgname); // Begruessung invers
      meldeInfo (std::cout, 0, "Die wirkliche Ausgabe erfolgt nun invers:");
      /* Hauptteil */
      retStat = abfrageSchleifePFBestimmungAusgabe (std::cout, argc, argv, frstParamUsed);
      if (anzFaktorZerlegungen == 1)
      { // Info, wenn Primfaktoren ueberhaupt berechnet werden sollten
        meldeInfo (std::cout, 1, "Es konnte keine sinnvolle Eingabe gefunden werden");
      }
      /* Verabschiedung */             // Auf den Bildschirm ..
      begruessung (std::cout, 0, prgname); // Abschied invers
      std::cout  << RESET << std::endl;
    } else if ((frstParamStr == "-h") || (frstParamStr == "--help"))
    { // * 4. Moeglichkeit: Hilfstext
      frstParamUsed = 1;               // 1. Parameter wurde verwendet
      std::cout << CLRSCR;             // Bildschirm loeschen
      /* Begruessung */                // Auf den Bildschirm ..
      begruessung (std::cout, 1, prgname); // Begruessung zur Hilfe
      /* Hilfstext-Ausgabe */
      ausgabeInfotext (std::cout, 1);  // Hilfs-Text ausgeben
      meldeInfo (std::cout, 0, "Ende Hilfs-Text.");
      /* Verabschiedung */             // Auf den Bildschirm ..
      begruessung (std::cout, 0, prgname); // Abschied von Hilfe
    } else if ((frstParamStr == "-v") || (frstParamStr == "--version"))
    { // * 5. Moeglichkeit: Versionsinfo
      frstParamUsed = 1;               // 1. Parameter wurde verwendet
      std::cout << CLRSCR;             // Bildschirm loeschen
      /* Begruessung */                // Auf den Bildschirm ..
      begruessung (std::cout, 1, prgname); // Begruessung zum Versionstext
      /* Versions-Ausgabe */
      ausgabeInfotext (std::cout, 0);  // Versions-Text ausgeben
      meldeInfo (std::cout, 0, "Ende Versions-Text.");
      /* Verabschiedung */             // Auf den Bildschirm ..
      begruessung (std::cout, 0, prgname); // Abschied vom Versionstext
    } else if ((frstParamStr == "-b") || (frstParamStr == "-vh") || (frstParamStr == "-hv"))
    { // * 6. Moeglichkeit: Versions- und Hilfs-Text
      frstParamUsed = 1;               // 1. Parameter wurde verwendet
      std::cout << CLRSCR;             // Bildschirm loeschen
      /* Begruessung */                // Auf den Bildschirm ..
      begruessung (std::cout, 1, prgname); // Begruessung zur vollen Info
      /* Versions-Ausgabe */
      ausgabeInfotext (std::cout, 0);  // Versions-Text ausgeben
      /* Hilfstext-Ausgabe */
      ausgabeInfotext (std::cout, 1);  // Hilfs-Text ausgeben
      meldeInfo (std::cout, 0, "Ende Versions- und Hilfs-Text.");
      /* Verabschiedung */             // Auf den Bildschirm ..
      begruessung (std::cout, 0, prgname); // Abschied von vollen Info
    } else
    { // * 7. Moeglichkeit: Bildschirm - Standard {= Default}, ohne Spezial-Par.
      farbLos = false;                 // Abschluss auf cout mit ANSI-Farben
      /* Begruessung */                // Auf den Bildschirm ..
      begruessung (std::cout, 1, prgname); // Begruessung zum Standardprogramm
      meldeInfo (std::cout, 0, "Die gesamte Ausgabe erfolgt auf dem Bildschirm ...");
      /* Hauptteil */
      retStat = abfrageSchleifePFBestimmungAusgabe (std::cout, argc, argv, frstParamUsed);
      if (anzFaktorZerlegungen == 1)
      { // Info, wenn Primfaktoren ueberhaupt berechnet werden sollten
        meldeInfo (std::cout, 1, "Es konnte keine sinnvolle Eingabe gefunden werden");
      }
      /* Verabschiedung */             // Auf den Bildschirm ..
      begruessung (std::cout, 0, prgname); // Abschied vom Standardprogramm
    }
    return retStat;                    // Programm-Ende mit Fehlercode-Rueckgabe
  } catch (...)                        // Ende des try-Blocks zur Ausnahme-Beh.
  {                                    // Behandlung der Ausnahmebedingung ...
    meldeInfo (std::cout, 1, "Das Programm fand eine seltsame Ausnahmebedingung in main");
    if (anzFaktorZerlegungen == 1)
      meldeInfo (std::cout, 1, "Es konnte keine sinnvolle Eingabe gefunden werden");
    return 1;
  }
  meldeInfo (std::cout, 1, "Das Programm sollte nicht an diese Stelle gelangen");
} // Ende: int main (int argc, const char* argv[])
/******************************************************************************
 * Das war's - ein erstes Hobby - C++ - Programm - nicht zu trivial ...  :))  *
 ******************************************************************************
 */

Erläuterung zum Quelltext

  Neu 09/2019   Die Phase 2 wurde am 03.09.2019 abgeschlossen – der neue Quellcode (vergrößert von 104 auf aktuell 775 Zeilen; also wieder größer als das bereits am 23.08.2019 überarbeitete Makefile  😳 ; von 41 auf nun 139 Zeilen und auch größer als die GPLv3‑Lizenz mit 674 Zeilen) mit den Erklärungen ist nun hier zu finden ... und es hat sich viel geändert:
Umstellen auf unsigned long long zur Zerlegung größerer Zahlen, Abfangen von (ich hoffe  😀 ) allen Eingabefehlern, Ausgabeumleitung inkl. Schreiben in eine Datei mit genauem Zeitstempel im Namen, viele Flags z.B. mit inverser oder monochromer Bildschirmausgabe, Ausgabe eines Hilfstextes und ANSI-Steuercodes über konstante String-Variablen, übersichtlichere Ausgabe der Primfaktoren, flexiblere Eingabe mit beliebigen Trennzeichen sowie erweiterte Kommentare ...
Das Programm ist mit Phase 2 weit genug gediehen, um damit einen C++-Einstiegskurs sinnvoll leiten zu können.  😤 
Da die Phase 1 deutlich kompakter ist, kann diese auch zuerst gelesen werden, erst danach die deutlich erweiterte Fassung und Erklärung zu Phase 2 hier.

Es gibt einiges zu beachten, bis man mit dem Quelltext etwas anfangen kann:

Ergänzungen 

Wie man nun auf der Kommandozeile mit dem Programm umgeht, d.h. die einzelnen Programmierschritte auslöst: den Quelltext editiert, diesen übersetzt und das ausführbare Programm aufruft, kann man oben beim Makefile nachlesen.
Hier die Ausgabe im xfce4-terminal (mit Startparameter 14.323232-696/435l0xAB, d.h. fünf Zahlen (mit Trennern . + - + / + l), sowie der Eingabe von erst 18446744073709551615 und dann von 25,99,1, d.h. zwei Zahlen und dem Abbruch-Code):
[Output des C++-Beispiel-Programms]

die Ausgabe im inversen Modus (mit den drei Startparametern -i, 666,-999,0xAFFE, sowie zum Abbrechen 1):
[Output des C++-Beispiel-Programms im Invers-Modus]

sowie die des Versions- und Hilfstextes durch make r -b:
[Output des C++-Beispiel-Programms bzgl. Versions- und Hilfsausgabe]

Das Beispielprogramm steht mit dem Makefile zum direkten Download weiter unten bereit.

Zum Binary, d.h. der Datei des ausführbaren Programms in Maschinencode, noch ein paar Infos bzw. wie man sich diese beschafft:

$ ls -al example_debug example
-rwxrwxr-x 1 jmb jmb 60464 Sep  7 15:14 example_debug
-rwxrwxr-x 1 jmb jmb 56616 Sep  7 15:14 example
# d.h. 60464 = 59,05 kB / 56616 = 55.29 kB Größe
$ file example
example: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=f59476f8f7efd2f9dc548143f262f53b1191c37e, for GNU/Linux 3.2.0, not stripped
$ strings example
# Ausgabe aller Zeichenketten (Strings) des Executables (viel: 649 Zeilen)
$ readelf -x .rodata example
# Anzeigen der statischen Strings
$ nm example
# Auflisten der Symbole
$ readelf -s example
# Anzeigen der Symbole
$ readelf -h example
# Ausgabe des ELF (Binärformat unter Linux) - Headers
$ readelf --relocs example
# Klären, ob das Binary ein positionsunabhängiges Executable ist bzgl. der Bibliotheken.
$ ldd example
# Liste dynamisch gelinkter Bibliotheken (hier sechs: linux-vdso.so.1, libstdc++.so.6, libm.so.6, libgcc_s.so.1, libc.so.6, /lib64/ld-linux-x86-64.so.2)
$ ldconfig -p | grep NAME_BIBLIOTHEK
# Nachschauen, ob eine bestimmte Bibliothek auf dem System installiert ist, z.B. bei `ldconfig -p | grep libm.so.6' u.a. `... => /lib/x86_64-linux-gnu/libc.so.6'.
$ /lib/x86_64-linux-gnu/libc.so.6
# liefert am Anfang: 'GNU C Library (Ubuntu GLIBC 2.29-0ubuntu2) stable release version 2.29.' auf Xubuntu 19.04.
$ objdump -d example
# Disassemblieren des Binary bzw. des Object Files (*.o)
$ strace ./example
# Lässt das Programm ablaufen und berichtet, was es dabei tut.

Dies sind ein paar Beispiele, wobei zumeist auf die GNU Binary Utilities (kurz binutils, ebenso Name des Pakets, in dem neben Assembler as und Linker ld diese Werkzeuge für ausführbare Dateien enthalten sind; diese werden, wie bereits ausgeführt, typischerweise gemeinsam mit gcc, make und gdb verwendet; vgl. Homepage sowie Dokumentation zu GNU Binutils; siehe auch Phoronix‑Artikel zu Version 2.34 vom 01.02.2020 bzw. zum Vorgänger 2.33.1 vom 12.10.2019) zurückgegriffen wurde.
Es wäre wünschenswert, wenn man einem Binary auch die Compilerversion und die benutzten Flags entlocken könnte, wie es ein aktuelles GCC‑Proposal gerade vorschlägt (vgl. GCC Developers Discuss New Option For Recording Compiler Flags / Details In Binaries, Phoronix, 16.11.2019).


Wer das Makefile, den Quelltext des obigen C++‑Beispielprogramms und die Executables für Linux haben möchte, kann es einfach als gezippte Tar‑Datei jmb_added_for_cpp_programming.tgz (52,2 kB, Fassung vom 07.09.2019 – d.h. komplette Phase 2) downloaden und an passender Stelle zum neuen Unterverzeichnis jmb mit den vier Dateien (Makefile, example_debug, example und example.cpp) entpacken über:   tar xvfz jmb_added_for_cpp_programming.tgz.
[Siehe auch Link zum Download der ehemaligen Phase 1 vom 08.08.2019.]
Link zu den 🐧empfohlenen Programmen.

Weiterführende Literatur & Online Quellen


Link zum 🐧persönlichen Hintergrund bzgl. GNU/Linux.

Eigene Erfahrungen und Motivation

Vor 1987 und dem ersten PC habe ich in den Ferien ein ausgeliehenes Schulbuch und die Anleitung zu Turbo Pascal gelesen. Mit dem Computer begann ich auch sofort, mein erstes Programm zu schreiben: ein Menüprogramm unter DOS, das sowohl Programme von Festplatte laden konnte als auch von 3.5" Diskette sowie die Uhrzeit über TSR-Routinen (Terminate and Stay Resident) sekundengenau anzeigte. Unter DOS nicht ganz einfach.
Als nächstes suchte ich geeignete BASIC-Spiele-Listings, tippte sie ab und brachte sie zum Laufen, um sie dann in Turbo Pascal erneut zu programmieren. Wegen Unzufriedenheit mit der Steuerung suchte ich den Tastaturpuffer, der bei IBM PS/2 nicht dokumentiert und nicht unter der Speicher-Adresse voriger PCs zu finden war ... inkl. ersten Reverse-Engineering-Methoden, bis dies erfolgreich mit Assembler-Routinen ansprechbar war. Dennoch habe ich das Pacman-Spiel, um das es ging, nie mit der angedachten hohen Auflösung (für damalige Zeiten, d.h. mehr als den MCGA-Standard: 320 × 200 pix) weiterentwickelt.
Daneben programmierte ich direkt den Laserdrucker, der eine deutlich höhere Auflösung mit 300 dpi (entspricht bei DIN A4 3508×2480 pel = 8,7 Mpel, ensprechend 4k = UHDTV1 = 3840×2160 pix = 8,3 Mpix) als der Bildschirm hatte (vgl. Bildschirm: Auflösung und Fläche der Seite 👑König Desktop).

An der Uni lernte ich für PraMa (heute würde man eher Numerik sagen) die Programmiersprache C, nachdem die Tutoren Turbo Pascal nicht verstanden – als es genauso zäh mit C lief, brach ich ab (ich bekam immer sehr gute Noten, die Erklärungen zogen sich aber meist eine Stunde hin, bis diese Tutoren den jeweiligen Algorithmus verstanden ...).
In der Forschung lernte ich dann auch Fortran kennen (und verwendete es neben C), allerding passte ich zumeist nur den Code zum Kompilieren auf DEC OSF-Alpha bzw. Debian 🐧GNU/Linux an, patchte SW zur astronomischen Bildverarbeitung und änderte vorhandene Programme ab.

Als ich in die IT-Branche eintrat, hatte ich nie einen Kurs, Vortrag, Vorlesung etc. über Computer oder Programmieren besucht: ich war reiner Autodidakt. Dies hat sich nicht negativ bemerkbar gemacht, eher positiv (ich hatte nie eine Sehnenscheiden-Entzündung, da ich das 10-Finger-System nicht erlernte). Allerdings war ich im professionellen Umfeld vorrangig mit Scripten beschäftigt, zumal ich überwiegend für proprietäre Unixe zuständig war. Und privat habe ich in dieser Zeit den Computer eher gemieden.

Ich habe mich nie als Programmierer gesehen – und suche nun ein paar Herausforderungen, um ggf. Heranwachsende für hochwertiges Programmieren zu interessieren – kein Java und Rumschubsen der Maus, worauf ich schon angesprochen wurde. Erste Anfänge dieser Idee sind hier bereits sichtbar ...
Ob ich tatsächlich wieder anfange, kleine Spiele zu programmieren, was mich jüngst zu C++ bewog, wird sich zeigen. Mein Interesse wird sich aber vorrangig auf C und C++ konzentrieren.


Link zu 🌠meiner generellen Motivation.
Bei Fragen / Problemen mit dieser Seite bzw. meiner Webpräsenz kontaktieren Sie mich bitte:
      E-Mail:  📧jmb@jmb-edu.de
© 1987-2020 JMB           Bitte beachten Sie hierzu auch mein 📄Impressum.                   W3C: Valid HTML 4.01 Transitional

[Zurück zur 🏁Startseite]
Erste Fassung:30. Juli 2019
Letzte Änderung: 29. Juni 2020