C Notes
Concepts
- RWTH Grundgebiete der Informatik 1
- Gegenstand der Vorlesung ist die Einführung in Programmiertechniken, Datenstrukturen und Algorithmen anhand von C.
- Grundlegende Programmelemente:
- Skalare und zusammengesetzte Datentypen, Anweisungen, Kontrollfluß, Funktionen, Klassen, C-Programmstruktur und Programmierumgebung;
- Programmanalyse:
- Wachstumsordnungen, Komplexitätsklassen, best/worst case Analyse;
- Lineare Datenstrukturen:
- Listen, Stacks, Queues, Iteration und Rekursion;
- Nichtlineare Datenstrukturen und Suchverfahren:
- Bäume, Graphen, Suchbäume, Hashtabellen;
- Algorithmenentwurf:
- Sortierverfahren, Heuristiken, Greedy-Algorithmen, grundlegende Optimierungsverfahren
About C
- C is a procedural language, whereas C++ is an object-oriented language
Documentation
- standard library
- On Unix-like systems, the authoritative documentation of the actually implemented API is provided in the form of
manpages. - On most systems, man pages on standard library functions are in section 3 (e.g.
man 3 malloc,man 3 puts, etc.) - Section 7 may contain some more generic pages on underlying concepts (e.g.
man 7 math_errorin Linux).
- On Unix-like systems, the authoritative documentation of the actually implemented API is provided in the form of
Build
Source Code
- Source code is generally understood to mean programming statements that are created by a programmer with a text editor or a visual programming tool and then saved in a file. washington.edu
Object Code
- With the help of an adequate compiler, for example GCC, source code is translated into object code, first.
Binary files (ELF files)
- ELF = "Executable and Linkable Format"
- see notes "Build" → "Binary vs Executable"
- What is an ELF file
- Eventually, the linker translates the object code into a binary file that links the object code with the referenced libraries.
- This binary file contains the single instructions as machine code that are understood by the CPU, and are executed as soon the compiled program is run.
- The binary file follows a specific structure, and one of the most common ones is named ELF that abbreviates Executable and Linkable Format.
- ELF is widely used for
- executable files
- relocatable object files
- shared libraries
- core dumps
- The specification does not clarify the filename extension for ELF files. In use is a variety of letter combinations, such as
- .axf,
- .bin,
- .elf,
- .o,
- .prx,
- .puff,
- .ko,
- .so, and
- .mod,
- or none.
man elfgives you a handy summary about the structure of an ELF file
ELF Header
- reading the ELF header:
readelf -h elf_file- read more about
readelfin (What is an ELF file)
- read more about
objdump -h elf_file
- The ELF header is 32 bytes long, and identifies the format of the file.
- It starts with a sequence of four unique bytes that are 0x7F followed by 0x45, 0x4c, and 0x46 which translates into the three letters E, L, and F.
- Among other values, the header also indicates
- whether it is an ELF file for 32 or 64-bit format,
- uses little or big endianness,
- shows the ELF version
- as well as for which operating system the file was compiled for in order to interoperate with the right application binary interface (ABI) and cpu instruction set.
Preprocessor
include
- difference between
#include <lib.h>and#include "lib.h"- What differs is the locations in which the preprocessor searches for the file to be included (stackoverflow)
- phth test: This matters because if you write
#include <lib.h>instead of#include "lib.h"the preprocessor will not findlib.h!!!
ifndef, Include Guards
From stackoverflow:
- Those are called include guards.
- Once the header is included, it checks if a unique value (in this case
HEADERFILE_H) is defined. Then if it's not defined, it defines it and continues to the rest of the page. - When the code is included again, the first
ifndeffails, resulting in a blank file. - That prevents double declaration of any identifiers such as types, enums and static variables.
- Also it prevents recursive inclusions... Imagine
alice.hincludesbob.handbob.hincludesalice.hand they don't have include guards... - Kevin Dungs
Simple Example: Example
Anschaulich:
#ifndefund#endifumschließen eine Art IF Block, der nur ausgeführt wird, wenn die conditionndef(not defined) TRUE ist.- Beim ersten include des header files wird über das
#define SOME_MAKROein Makro definiert. - Beim zweiten include desselben files ist
#ifndef SOME_MAKROFALSE und der "IF Block" wird geskipped.
Operator Precedence, Associativity
- When multiple operators of the same precedence level are used in an expression, they are evaluated according to their associativity (source)
Variables
Initialization
see Different ways to initialize a variable in C/C++
Global variables (aka External variables)
- external variable: a variable defined outside of any function
- scope: the scope is the entire source code file that contains the variable definition
- If you don't explicitly initialize an external variable when it's defined, the compiler initializes it to 0.
- this is different than local variables!
Best practice
- use them rarely
- because usage violates the principle of modular independence
- i.e each function should be self-contained/modular
- because usage violates the principle of modular independence
- use them only when all or most of the program's functions need access to the variable
- When a function uses an external variable, it is good programming practice to declare the variable within the function using the
externkeyword (C: see Storage-class specifiers; C++: see Storage-class specifiers)- "You normally use
externwhen you want to refer to something that is not in the current translation unit, such as a variable that's defined in a library you will be linking to." (stackoverflow)
- "You normally use
Local variables
- aren't automatically initialized to 0 by the compiler
- if not initialized they have an undefined or garbage value
Static variables vs. Automatic variables
- local variables are automatic by default, i.e.
- created anew each time the function is called
- destroyed when execution leaves the function
- thus doesn't retain its value between calls to the function in which it is defined.
- In order for a local variable to retain its value between function calls, it must be defined as static with the
statickeyword.void print(int x)
{
static int lineCount;
/* Additional code goes here */
} - (optional) if you want to, you can include the
autokeyword in the definitionvoid func1(int y)
{
auto int count;
/* Additional code goes here */
} staticandautoare Storage-class specifiers (see below)
Storage Duration
From cppreference ("C language" part): Storage duration
- Storage-class specifiers: Specify storage duration (...) of objects and functions.
auto(automatic storage duration)register(automatic storage duration)static(static storage duration)extern(static storage duration)_Thread_local(thread storage duration)
- Storage duration: Every object has a property called storage duration, which limits the object lifetime. There are four kinds of storage duration in C:
-
static
- The storage duration is the entire execution of the program
- All objects declared static have this storage duration.
-
automatic
- The storage is allocated when the block in which the object was declared is entered and deallocated when it is exited by any means (goto, return, reaching the end).
- All function parameters and non-static block-scope objects have this storage duration, as well as compound literals used at block scope.
-
thread
- The storage duration is the entire execution of the thread in which it was created
-
allocated
- The storage is allocated and deallocated on request, using dynamic memory allocation functions
-
static
Int
- don't convert
inttofloat- when an integer type is converted to a floating-point type, the resulting floating-point value might not exactly match the integer value (e.g.
2.999995instead of3) - keep integer values in type
short,int,long, or variableslong long
- when an integer type is converted to a floating-point type, the resulting floating-point value might not exactly match the integer value (e.g.
Low-order byte and High-order byte
From stackoverflow:
- In C, the size of an
intis implementation defined, but is usually 2, or 4 bytes in size. The high-order byte would be the byte that contains the largest portion of the value. The low-order byte would be the byte that contains the smallest portion of the value.- For example, if you have a 16-bit
int, and the value is5,243, you'd write that in hex as0x147B(phth: 4 hex Stellen, weil 16-bitint).- The high order byte is the
0x14(phth: 1 byte = 8 bit, sodass 256 Werte möglich sind, was in hex 2 Ziffern sind! Daher die14und nicht nur die1hinter dem0x.), and - the low-order byte is the
0x7B.
- The high order byte is the
- A
charis only 1 byte, so it is always contained within the lowest order byte. When written in hex (in left-to-right fashion)- the low-order byte will always be the right-most 2 digits, and
- the high-order byte will be the left-most 2 digits (assuming they write all the bytes out, including leading 0s).
- For example, if you have a 16-bit
Arrays
Definition
- array: a collection of data storage locations, each storing the same type of data and having the same name
- array elements: the storage locations of an array
Declaration
- When you declare an array, the compiler sets aside a block of memory large enough to hold the entire array.
- Individual array elements are stored in sequential memory locations.
Initialization
- You can initialize all or part of an array when you first declare it
- ANSI standard: the elements that are not initialized will be set to zero.
- however, it is best to make sure you know what a value is initialized to by setting it yourself.
- ANSI standard: the elements that are not initialized will be set to zero.
- If you omit the array size, the compiler creates an array just large enough
- If you include too many initializers, the compiler detects an error.
- Note: This is different from using subscripts which are out of bounds which will not be detected by the compiler!
Subscripts
- the last element is one less than the number of elements in the array.
Passing arrays to a function
- problem 1: The only way you can pass an array to a function is by using a pointer to an array. (see Passing Function arguments)
- problem 2: function that can handle arrays of different sizes (e.g. a function that finds the largest element in an array of integers)
- How does the function know the size of the array whose address it was passed?
- method 1: identify the last array element by storing a special value there (see Strings)
- method 2: Pass the function the array size as an argument
- How does the function know the size of the array whose address it was passed?
Copy 2d array and assign it to another
From stackoverflow:
char arr[3][3] = { {'a','b','c'},{'d','e','f'},{'g','h','i'} };
char arr_copy[3][3];
memcpy(arr_copy, arr, sizeof arr_copy);
Out of bounds error
- The C compiler doesn't recognize whether your program uses an array subscript that is out of bounds.
- Your program compiles and links, but out-of-range subscripts generally produce erroneous results
- from source:
- though,
clangwill do it for some cases if you pass-fcatch-undefined-behavior - The
gccequivalent is-fsanitize=undefined. The-ggdbflag also helps; it causes compilation with extra debugging info, which can be the difference between having/not having line numbers pointing to the error in the code infsanitizeroutput. - but this does not mean it is part of the C Standard!
- though,
- from source:
Sometimes Dynamically Allocated Integer Arrays Default Initialize to 0
- Why do dynamically allocated integer arrays sometimes appear to be default initialized to 0 ?
Best practice
- use
#definestatements to create constants that can be used when declaring arrays. Then you can easily change the number of elements in the array. - do not use multidimensional arrays with more than three dimensions
Functions
Return statements
- in the
mainfunction areturn 0;is optional in C99, i.e.return 0;will be returned by default if there is no explicit return statement at the end of the function stackoverflow
Inline Functions
- from What is the use of the inline keyword in C?
- A C code can be optimized in two ways: For Code size and for Execution Time.
- So, it tells the compiler to build the function into the code where it is used with the intention of improving execution time.
- If you declare small functions like setting/clearing a flag or some bit toggle which are performed repeatedly,
inline, it can make a big performance difference with respect to time, but at the cost of code size.
Best practice
- use
void foo(void);to say "no parameters" in C, and it also works in C++. (why this is important)
Program Input/Output, Devices
- input
- Data moved from an external location into RAM, where the program can access it
- output
- Data sent to a location external to the program (common destinations: screen, printer, disk files)
- devices
- collective term for input sources and output destinations
Streams
- C carries out all input and output operations by means of streams
- stream: a sequence of bytes of data (e.g. a sequence of characters)
- input stream: sequence of bytes flowing into a program
- output stream: sequence of bytes flowing out of a program
- advantage: streams make input/output programming device independent
- file: every C stream is connected to a file, which is an intermediate step between the stream that your program deals with and the actual physical device being used for input or output
- the details of interactions between streams, files, and devices are taken care of automatically by the C library functions and the operating system
-
2 modes of C streams
- text stream: consists only of characters (e.g. text data)
- binary stream: consists of any sort of data
Five Standard Streams
(the first 3 are C ANSI standard, the last 2 are only under Windows and DOS)
| Format Specifier | Type | Device |
|---|---|---|
| stdin | Standard input | Keyboard |
| stdout | Standard output | Screen |
| stderr | Standard error | Screen |
| stdprn* | Standard printer | Printer (LPT1:) |
| stdaux* | Standard auxiliary | Serial port (COM1:) |
Stream Functions
- functions that deal with stream input and output
Input Functions
- hierarchy of 3 levels
- character input (
getchar,getch,getche,getc,fgetc,ungetc) - line input (
gets,fgets) - formatted input (
scanf,fflush)
- character input (
Output Functions
- 3 categories
- character output (
putchar,putc,fputc) - line output (
puts,fputs) - formatted output (
printf,fprintf)
- character output (
printf
Note: Programmers often add print statements during debugging. Such statements should always flush the stream. Otherwise, if the program crashes, output may be left in the buffer, leading to incorrect inferences about where the program crashed.
Understanding the printf Declaration
- prototype:
int printf(const char *restrict format, ...);- Ellipsis in the declaration
- the ellipsis notation in the declaration of
printf()means that default argument promotions occur, but that affects only thefloattype and integer types smaller thanint(short,char, etc). - from What is ellipsis operator in c:
- An ellipsis is used to represent a variable number of parameters to a function. For example
void format(const char* fmt, ...)in C could then be called with different types and numbers of parameters such asformat("%d-%d-%d", 2010, 9, 25);andformat("product: %s, price: %f", "HDD", 450.90);.
- An ellipsis is used to represent a variable number of parameters to a function. For example
- the ellipsis notation in the declaration of
- Ellipsis in the declaration
Escape sequences
Conversion command
%[flag][field_width][.[precision]][l]conversion_char
Components of the command in brackets are optional.
Format specifiers
| Format Specifier | Type |
|---|---|
%c | Character |
%d | Signed integer |
%e or %E | Scientific notation of floats |
%f | Float values |
%g or %G | Similar as %e or %E |
%hi | Signed integer (short) |
%hu | Unsigned Integer (short) |
%i | Unsigned integer |
%l or %ld or %li | Long |
%lf | Double |
%Lf | Long double |
%lu | Unsigned int or unsigned long |
%lli or %lld | Long long |
%llu | Unsigned long long |
%o | Octal representation |
%x or %X | Hexadecimal representation |
%p | Pointer |
%s | String |
%u | Unsigned int |
%n | Prints nothing |
%% | Prints % character |
These are the basic format specifiers. We can add some other parts with the format specifiers. These are like below
- A minus symbol (
-) sign tells left alignment - A number after
%specifies the minimum field width. If string is less than the width, it will be filled with spaces - A period (
.) is used to separate field width and precision
Precision
From C99 §7.19.6.1:
- A
doubleargument representing a floating-point number is converted to decimal notation in the style[−]ddd.ddd, where the number of digits after the decimal-point character is equal to the precision specification. - If the precision is missing, it is taken as 6; if the precision is zero and the
#flag is not specified, no decimal-point character appears. If a decimal-point character appears, at least one digit appears before it. - The value is rounded to the appropriate number of digits.
Field width
Determines the minimum number of characters output:
- decimal integer not starting with 0: output is padded on the left with spaces
- decimal integer starting with 0: output is padded on the left with zeros
- the
*character: the value of the next argument ofprintf()(which must be anint) is used as field width, e.g. inprintf("%*d", w, a);the integerwspecifies the field width - no field width or field width narrower than output: field width just as wide as needed
Flag
+: display signed numbers with leading+or-
fprintf
- same as
printfexcept thatprintfalways uses thestdout, whereasfprintfspecifies the output stream - generally, used for output to disk files
Usage
-
disk files
-
fprintf(stderr, "An error has occurred.");or for better maintainability:error_message("An error has occurred.");
void error_message(char *msg)
{
fprintf(stderr, msg);
}this way, only
void error_message()needs to be changed in order to direct the error messages to some other destination.
Redirection
-
redirect output:
./program > output.txtredirectsstdoutof./programtooutput.txt(overwritesoutput.txtif it exists already!) -
redirect input:
./program < input.txt(source code for./programsee below) will takeinput.txtas input rather than asking the user for input via keyboard/* program.c */
#include <stdio.h>
int main( void )
{
char buf[80];
gets(buf);
printf("The input was: %s\n", buf);
return 0;
} -
>>: redirectsstdoutto a file and appends it to the file (instead of overwriting the file like>)
scanf, gets, fgets
Read all characters up to the next newline character ('\n', must use single quotes else it is a string literal).
Control Characters
- EOF: If you're typing at the terminal and you want to provoke an end-of-file, use CTRL-D (unix-style systems) or CTRL-Z (Windows).
scanf
- limitations:
scanf()cannot take strings with spaces in between characters as input,gets()andfgets()can
gets
- prototype:
char *gets(char *str); - input stream is read from
stdinuntil- a newline (
'\n') or - end-of-file (see Control Characters) is encountered
- a newline (
- the newline (
'\n') is replaced with a null character ('\0') - return value:
- a pointer to the string (the same as
str) - if
gets()encounters an error or reads end-of-file: a null pointer
- a pointer to the string (the same as
- the string is stored at the location indicated by
str - important: allocate sufficient memory space to store the string before calling
gets()
fgets
- prototype:
char *fgets(char *str, int n, FILE *fp);FILE *fpis used to specify the input stream (e.g.stdin)strindicates where the input string is storednspecifies the maximum number of characters to be input
- input stream is read until
- a newline (
'\n') or - end-of-line is encountered or
n - 1characters have been read
- a newline (
- the newline (
'\n') is included in the string (different fromgets()!) and a'\0'is appended before it is stored (i.e. strings are stored with trailing'\n\0') - the return values are the same as for
gets() - the string is stored at the location indicated by
str
Char
- use single quotations when initializing a variable
- char constants:
- literal:
- enclose a single character in single quotation marks
char code = 'x';
- enclose a single character in single quotation marks
- symbolic:
- using
#define#define EX 'x'
char code = EX; /* Sets code equal to 'x' */ - using
constconst char A = 'Z';
- using
- literal:
- When you store the character
ain a typecharvariable, the compiler is really storing the ASCII value97 - if you want to print the extended ASCII (aka CP437) characters, you must use an
unsigned char- not good practice, use UTF-8 instead (see mapping from extended ASCII to UTF-8)
Unicode vs UTF-8
Important for:
- in NLP:
- BPE (note from GPT-2 paper: "Despite its name, reference BPE implementations often operate on Unicode code points and not byte sequences.")
- byte-level BPE
Remember:
- Unicode defines the character code/code point (each character code is a hex number in the range 0x0 to 0x10FFFF) (Unicode Planes: see U+0000 (first char) and U+10FFFF (last char))
- UTF-8 defines the byte code/byte sequence for storage (each byte code has 1 to 4 bytes; each byte is represented by 2 hex characters) (UTF-8 hex table)
from (Wiki: Unicode):
- defines as of the current version (Unicode v15.0) 149,186 characters
- Unicode can be stored using several different encodings, which translate the character codes into sequences of bytes (Recall: 1 byte = 2 Hex characters).
- The Unicode standard defines three and several other encodings exist, all in practice variable-length encodings.
- The most common encodings are the ASCII-compatible UTF-8, (...).
from (Wiki: UTF-8):
- Extension of ASCII
- UTF-8 is capable of encoding all 1,112,064 valid character code points in Unicode using one to four one-byte (8-bit) code units.
- Code points with lower numerical values, which tend to occur more frequently, are encoded using fewer bytes.
- It was designed for backward compatibility with ASCII: the first 128 characters of Unicode, which correspond one-to-one with ASCII, are encoded using a single byte with the same binary value as ASCII, so that valid ASCII text is valid UTF-8-encoded Unicode as well.
Strings
Definitions
- a string is a sequence of characters delineated by
- a pointer that points to the string's first character and
- a null character
\0that indicates the end of the string ("null-terminated" string)
- From C18 6.4.5:
- A character string literal is a sequence of zero or more multibyte characters enclosed in double-quotes, as in
"xyz". - A UTF–8 string literal is the same, except prefixed by
u8. - A wide string literal is the same, except prefixed by the letter
L,u, orU.
- A character string literal is a sequence of zero or more multibyte characters enclosed in double-quotes, as in
String Literals
String Literals sind "besondere" char arrays, die im read-only memory sein können, aber nicht müssen (abhängig von C implementation). Nach ISO C dürfen sie nicht modifiziert werden!
From cppreference:
- character string literal: The type of the literal is
char[N], whereNis the size of the string in code units of the execution narrow encoding, including the null terminator. - A string literal is not necessarily a string; if a string literal has embedded null characters, it represents an array which contains more than one string:
char* p = "abc\0def"; // strlen(p) == 3, but the array has size 8 - String literals are not modifiable (and in fact may be placed in read-only memory such as
.rodata). If a program attempts to modify the static array formed by a string literal, the behavior is undefined.phth note: The latter variant is treated aschar* p = "Hello";
p[1] = 'M'; // Undefined behavior
char a[] = "Hello";
a[1] = 'M'; // OK: a is not a string literalthus,char a[] = { 'H', 'e', 'l', 'l', 'o', '\0'};
a[1] = 'M'; // OK: a is not a string literalchar a[] = "Hello";copies eachcharbyte of the string literal"Hello"into the newly created memory block of the arraya, but the memory block storing"Hello"is not modified!ais a standard array, thus, like all standard arrays, it is modifiable.apoints to the array's first element'H'and not to the string literal"Hello"'s first element!- from cppreference:
- Successive bytes of the string literal (...), including the terminating null byte/character, initialize the elements of the array
- from cppreference:
Notes:
- phth:
.rodatais the "read-only data segment", a portion of an object file or the corresponding address space of a program that contains static constants. - phth: "may be placed in read-only memory" means they do not have to!
From the C89 Rationale, 3.1.4 String literals:
- String literals are specified to be unmodifiable.
- This specification allows implementations (phth: z.B.
gcc,clang, etc)- to share copies of strings with identical text,
- to place string literals in read-only memory, and
- d.h. ob string literals in read-only memory sind oder nicht ist abhängig von der implementation
- perform certain optimizations.
- However, string literals do not have the type array of
const char,- in order to avoid the problems of pointer type checking, particularly with library functions, since assigning a pointer-to-
const charto a plain pointer-to-charis not valid.
- in order to avoid the problems of pointer type checking, particularly with library functions, since assigning a pointer-to-
- Those members of the Committee who insisted that string literals should be modifiable were content to have this practice designated a common extension (see F.5.5).
FAQ - string literals
Good example code:
- see first two answers in If char*s are read only, why can I overwrite them?
- explains: char *str and char str[] are not the same
- Warning: this stackoverflow answer is wrong!
- proof: see below: comment by RobertS supports Monica Cellio, 13949 reputation: "'Short answer: they are basically the same.' - No, they aren't. Both methods are completely different."
- the same from a compiler perspective: Why string is not modifiable in new compilers
Create Strings as char arrays
element-by-element:
char string[10] = { 'A', 'l', 'a', 'b', 'a', 'm', 'a', '\0' };
using string literal:
char string[10] = "Alabama";
char message[] = "Great Caesar\'s Ghost!";
- use double quotes
- The array's name is a type
charpointer to the beginning of the string - the compiler calculates the size of the array for you, if no subscript is provided
- Note: The expression within the brackets
[]is referred to as a subscript
- Note: The expression within the brackets
- the compiler automatically adds the terminating null character at the end of the string
Create Strings without arrays
Allocate string space at compilation
static memory allocation: explicitly allocating memory in the program source code by declaring variables, structures, and arrays
char *message = "Great Caesar\'s Ghost!";
i.e. *message and message[] are equivalent!
- wrong: why they are different
Note:
- The pointer
*messageitself is stored at a different location than the literal string"Great Caesar\'s Ghost!"(see section Pointer). - The array's name is a type
charpointer to the beginning of the string
Allocate string space at runtime (malloc)
dynamic memory allocation:
##include <stdlib.h>
##include <stdio.h>
int main( void )
{
/* allocate memory for a 100-character string */
char *str;
str = (char *) malloc(100 * sizeof(char));
if (str == NULL)
{
printf( "Not enough memory to allocate buffer\n");
exit(1);
}
printf( "String was allocated!\n" );
return 0;
}
Note that void *malloc() is typecast so that its return value is the proper type - a pointer to a char variable. Further details about malloc() are under section Memory.
Best Practices
- it's a good habit to use "\t" instead of a real tab
- why?
- in vim when using ":retab" on a program, it may change white space inside a string constant (see vim doc)
- why?
Pointer
- The address of a variable is actually the address of the first (lowest) byte it occupies
- Each pointer is equal to the address of the first byte of the pointed-to variable
Size of pointers
- The size of a pointer in C/C++ depends upon different issues like Operating system, CPU architecture etc.
- Usually it depends upon the word size of underlying processor, for example
- for a 32 bit computer the pointer size can be 4 bytes
- for a 64 bit computer the pointer size can be 8 bytes.
- So, for a specific architecture pointer size will be fixed.
- It is common to all data types like
int *,float *etc.
Data type of pointers
From stackoverflow:
- But really, what is a type? The gist of it is that a type is a way to determine how data in memory is supposed to be interpreted. A variable of the type
charrepresents a character. A variable of the typeintrepresents an integer. Same applies to pointers:char *xis not of the typechar, it's of the typechar *(pointer tochar). This means thatchar *xitself is a location in memory we can use to read one or morecharvalues. - TL;TR: Yes, a pointer is a data type (
void *in its purest form). The pure form is quite unusable (because you can't dereference it). Instead of having to cast the pointer every time you decide to use it, C offers the convenience of derived pointer types (likechar *,int *and so on). But really, they're pointers, and therefore a data-type in their own right.
Where is a pointer stored?
- a pointer can be stored anywhere in the process' virtual memory (aka address space) which is typically organized in 6 sections:
- environment section - used to store environment variables and command line arguments;
- the stack, used to store memory for function arguments, return values, and automatic variables;
- the heap (free store) used for dynamic allocation,
- data section for uninitialized static and global variables
- data section for initialized static and global variables
- a text section where the actual code is kept
- more precisely: from cprogramming.com:
- The size of the virtual memory is the maximum size of the maximum size your computer can address using pointers (usually on a 32-bit processor each process can address 4 GB of memory).
- Typically, in each process, the virtual memory available to that process is called its address space.
- Even though every application has its own 4 GB (on 32-bit systems) of virtual memory, that does not necessarily mean that your program can actually use all of that memory.
- On Windows, the upper 2 GB of that memory are allocated to the operating system kernel, and are unavailable to the process.
- (Therefore, any pointer starting with 0x8xxxxxxx is unavailable in user space.)
- On Linux, the upper 1 GB is kernel address space
- On Windows, the upper 2 GB of that memory are allocated to the operating system kernel, and are unavailable to the process.
- stackoverflow:
- a pointer can be created in any of these storage classes; and objects being pointed to can also be in any of these storage classes
- Roughly,
dynamiccorresponds to "heap", andautomaticcorresponds to "stack".- e.g.
malloc()dynamically allocates memory from the heap
- e.g.
void pointer
In C++
A void pointer can point to objects of any data type:
int nValue;
float fValue;
struct Something
{
int n;
float f;
};
Something sValue;
void* ptr;
ptr = &nValue; // valid
ptr = &fValue; // valid
ptr = &sValue; // valid
However, because the void pointer does not know what type of object it is pointing to, dereferencing a void pointer is illegal. Instead, the void pointer must first be cast to another pointer type before the dereference can be performed.
int value{ 5 };
void* voidPtr{ &value };
// std::cout << *voidPtr << '\n'; // illegal: dereference of void pointer
int* intPtr{ static_cast<int*>(voidPtr) }; // however, if we cast our void pointer to an int pointer...
std::cout << *intPtr << '\n'; // then we can dereference the result
NULL pointer
Definition:
##define NULL ( (void *) 0)
C11 standard definition:
- An integer constant expression with the value
0, or such an expression cast to typevoid *, is called a null pointer constant. - If a null pointer constant is converted to a pointer type, the resulting pointer, called a null pointer, is guaranteed to compare unequal to a pointer to any object or function.
Usage
- To initialize a pointer variable when that pointer variable hasn't been assigned any valid memory address yet.
int * pInt = NULL;
- To check for a null pointer before accessing any pointer variable. By doing so, we can perform error handling in pointer related code, e.g., dereference a pointer variable only if it's not
NULL.
if(pInt != NULL) /* We could use if(pInt) as well */
{
/* Some code */
}
else
{
/* Some code */
}
- To pass a null pointer to a function argument when we don't want to pass any valid memory address.
int fun(int *ptr)
{
/*Fun specific stuff is done with ptr here*/
return 10;
}
fun(NULL);
Difference between a void pointer and a NULL pointer?
From stackoverflow:
In C, there is void, void pointer and NULL pointer.
voidis absence of type. I.E. a function returning avoidtype is a function that returns nothing.voidpointer: is a pointer to a memory location whose type can be anything: a structure, an int, a float, you name it.- A
NULLpointer is a pointer to location0x00, that is, no location. Pointing to nothing.
Examples:
void function:
void printHello()
{
printf("Hello");
}
void pointer:
void *malloc(size_t si)
{
// malloc is a function that could return a pointer to anything
}
NULL pointer:
char *s = NULL;
// s pointer points to nowhere (nothing)
Pointer arithmetic
If you have a pointer ptr that points to a data object of type obj,
ptr++
has the same effect as
ptr += sizeof(obj);
// https://stackoverflow.com/a/8208106
// given:
int *p;
int a = 100;
p = &a;
// then:
ptr++; // Pointer moves to the next int position (as if it was an array)
++ptr; // Pointer moves to the next int position (as if it was an array)
++*ptr; // The value pointed at by ptr is incremented
++(*ptr); // The value pointed at by ptr is incremented
++*(ptr); // The value pointed at by ptr is incremented
*ptr++; // Pointer moves to the next int position (as if it was an array). But returns the old content
(*ptr)++; // The value pointed at by ptr is incremented
*(ptr)++; // Pointer moves to the next int position (as if it was an array). But returns the old content
*++ptr; // Pointer moves to the next int position, and then get's accessed, with your code, segfault
*(++ptr); // Pointer moves to the next int position, and then get's accessed, with your code, segfault
Address of a pointer / multiple indirection
- multiple indirection: You can use the address-of operator to find the address of a pointer, so you can have pointers to pointers.
- The most common use involves arrays of pointers.
- The most common use of an array of pointers is with strings.
- The most common use involves arrays of pointers.
Passing Function arguments
- Using "passing by reference" (coll.) we can overcome the following two limitations of functions:
- Arguments are passed by value, and
- the return statement can return only one value
Structs
3 ways to access a structure member
- Using the structure name
- Using a pointer to the structure with the indirection operator (
*) - Using a pointer to the structure with the indirect membership operator (
->)- sometimes called "structure pointer operator"
Therefore, if p_str is a pointer to the structure str,
str.memb
(*p_str).memb
p_str->memb
are equivalent.
typedef
Whether you use typedef or a structure tag to declare structures makes little difference
typedef struct {
int x;
int y;
} coord;
or using a structure tag
struct coord {
int x;
int y;
};
Type Conversions
Type Promotion (implicit conversion, automatic conversion)
If the components of an expression have different types, an expression has the same type as its most comprehensive component. From least-comprehensive to most-comprehensive, the numerical data types are
- char
- short
- int
- long
- long long
- float
- double
- long double
Note: Promotion means that a copy of the variable is created and used in the expression evaluation. The type of the variable is not changed!
Promotion by Assignment: expression on the right side of an assignment statement is always promoted to the type of the data object on the left side of the assignment operator
- may cause demotion
- e.g.
floattoint(Caution: fractional part is lost!)
- e.g.
Cast / Typecast (explicit conversion)
- e.g.
(float)iwheniis an integer - can be performed on arithmetic expressions and pointers
- makes an internal copy of the variable using the new type (similar to promotion)
- most common use:
f1 = (float)i1/i2;to avoid using the fractional part in an integer division- casting one of the
intvariables is sufficient
- casting one of the
- casting pointers:
- Before you can use a
voidpointer, you must cast it to the proper type - don't need to cast any pointer
- in order to assign a value to it
- to compare it with
NULL
- must cast a pointer
- before dereferencing
- before performing pointer arithmetic with it
- Before you can use a
- Casting a variable expression to
voidto suppress this ("unused local variables or function parameters") warning has become an idiom in the C and later C++ community, stackoverflow- The Standard says in §5.2.9/4 says: "Any expression can be explicitly converted to type
cv void. The expression value is discarded.", stackoverflow
- The Standard says in §5.2.9/4 says: "Any expression can be explicitly converted to type
Memory
Monitoring of memory
Read How do I see how much free ram I really have? first! Also read section "When should I start to worry?"!
watch free -m
Bad signs
dmesg -T | grep oom-killer
## same as
dmesg --ctime | grep oom-killer
should not show any output! If it does, it is a bad sign!
- the
-T(same as--ctime) flag is used to print human-readable timestamps- Be aware that the timestamp could be inaccurate! (see
man dmesg)
- Be aware that the timestamp could be inaccurate! (see
Areas of memory
When a program begins the operating system sets up following areas of memory (depending on the requirements of the compiler):
- registers,
- code space,
- stack,
- global namespace, and
- free store (heap)