C++ Tutorial: Keyword - 2020
Q: What're the right forms of main()?
- void main()
- static int main()
- int main(char** argv, int argc)
- int main(int argc, char** argv)
- int main()
- inline int main(int argc, char* argv[])
- int main(char* argv[], int argc)
- int main(int argc, char* argv[])
- int main(int argc, char* argv[], char* options[])
Ans: 5, 8, 9
main() must return an int, and must have either no parameters or (int argc , char* argv[]) as its first set of parameters. A program that declares main() to be inline or static is ill-formed.
Keyword | Description |
---|---|
and | alternative to && operator |
and_eq | alternative to &= operator |
asm | insert an assembly instruction |
auto | declare a local variable, or we can let the compiler to deduce the type of the variable from the initialization. |
bitand | alternative to bitwise & operator |
bitor | alternative to | operator |
bool | declare a boolean variable |
break | break out of a loop |
case | a block of code in a switch statement |
catch | handles exceptions from throw |
char | declare a character variable |
class | declare a class |
compl | alternative to ~ operator |
const | declare immutable data or functions that do not change data |
const_cast | cast from const variables |
continue | bypass iterations of a loop |
default | default handler in a case statement |
#define | All header files should have #define guards to prevent multiple inclusion. |
delete | make dynamic memory available |
do | looping construct |
double | declare a double precision floating-point variable |
dynamic_cast | perform runtime casts |
else | alternate case for an if statement |
enum | create enumeration types |
exit() | ending a process |
explicit | only use constructors when they exactly match |
export | allows template definitions to be separated from their declarations |
extern | declares a variable or function and specifies that it has external linkage |
extern "C" | enables C function call from C++ by forcing C-linkage |
false | a constant representing the boolean false value |
float | declare a floating-point variable |
for | looping construct | friend | grant non-member function access to private data |
goto | jump to a different part of the program |
if | execute code based on the result of a test |
inline | optimize calls to short functions |
int | declare an integer variable |
long | declare a long integer variable |
mutable | override a const variable |
namespace | partition the global namespace by defining a scope |
new | allocate dynamic memory for a new variable |
not | alternative to ! operator |
not_eq | alternative to != operator |
operator | create overloaded operator functions |
or | alternative to || operator |
or_eq | alternative to |= operator |
private | declare private members of a class |
protected | declare protected members of a class |
public | declare public members of a class |
register | request that a variable be optimized for speed |
reinterpret_cast | change the type of a variable |
short | declare a short integer variable |
signed | modify variable type declarations |
sizeof | return the size of a variable or type |
static | create permanent storage for a variable |
static_cast | perform a nonpolymorphic cast |
struct | define a new structure |
switch | execute code based on different possible values for a variable |
template | create generic functions |
this | a pointer to the current object |
throw | throws an exception |
true | a constant representing the boolean true value |
try | execute code that can throw an exception |
typedef | create a new type name from an existing type |
typeid | describes an object |
typename | declare a class or undefined type |
union | a structure that assigns multiple variables to the same memory location |
unsigned | declare an unsigned integer variable |
using | import complete or partial namespaces into the current scope |
virtual | create a function that can be overridden by a derived class |
void | declare functions or data with no associated data type |
volatile | warn the compiler about variables that can be modified unexpectedly |
void | declare functions or data with no associated data type |
wchar_t | declare a wide-character variable |
while | looping construct |
xor | alternative to ^ operator |
xor_eq | alternative to ^= operator |
With C++ 11, we can use auto to declare types that can be deduced from context so that we can iterate over complex data structures conveniently. It is no longer a storage specifier which is used to put the auto variable to a stack. Actually, this usage as a storage specifier has never been widely used since its inception.
So, the following code
for(vector<int>::iterator it = v.begin(); ...) cout << *it << '\t'; cout << endl;
can be simplified by using auto:
for(auto it = v.begin(); ...)
Here is another sample for the usage of auto:
#include <iostream> #include <list> using namespace std; ostream& operator<< (ostream& os, const list<int>&lst;) { for(auto elm: lst) { os << " " << elm; } return os; } int main () { list<int> listA; list<int> listB; for(int i = 1; i < 6; i++) listA.push_back(i); for(int i = 1; i < 6; i++) listB.push_back(i*100); cout << "listA: " << listA << endl; cout << "listB: " << listB << endl; auto iter = listA.begin(); advance(iter, 3); /* Moves all elements from listB into *this. The elements are inserted before the element pointed by iter. The container listB becomes empty after the operation. */ listA.splice(iter, listB); cout << "listA: " << listA << endl; cout << "listB: " << listB << endl; /* Moves the elements in the range [iter, listA.end()) from listA into *this. The elements are inserted before the element pointed to by listB.begin(). */ listB.splice(listB.begin(), listA, iter, listA.end()); cout << "listA: " << listA << endl; cout << "listB: " << listB << endl; return 0; }
Output:
listA: 1 2 3 4 5 listB: 100 200 300 400 500 listA: 1 2 3 100 200 300 400 500 4 5 listB: listA: 1 2 3 100 200 300 400 500 listB: 4 5
const qualifier allows us to ask compiler to enforce a semantic constraint: a particular object should not be modified. It also allows us to tell other programmers that a value should remain invariant. The general form for creating a constant is:
const type name = value;
Note that we initialize a const in the declaration. So, the following line is an error:
const int cint; cint = 10; // too late
We'll get an error message something like this:
error: 'cint' : const object must be initialized if not extern error: 'cint' : you cannot assign to a variable that is const
If we don't provide a value when we declare the constant, it ends up with an unspecified value that we cannot modify.
What is const?
If we answer that question with "const is constant."
We may get 0 point for that answer.
If we answer with "const is read only."
Then, we may get some points.
Const correctness refers to use of the C++ const keyword to declare a variable or method as immutable. It is a compile-time construct that can be used to maintain the correctness of code that shouldn't modify certain variables. We can define variables as const, to indicate that they should not be modified, and we can also define methods as const, to mean that they should not modify any member variables of the class. Using const correctness is simply good programming practice. It can also provide documentation on the intent of our methods, and hence make them easier to use.
For pointers, we can specify whether the pointer itself is const, the data it points to is const, both, or neither:
char str[] = "constantness"; char *p = str; //non-const pointer to non-const data const char *pc = str; //non-const pointer to const data char * const cp = str; //const pointer to non-const data const char * const cpc = str; //const pointer to const data
When const appears to the left of the *, what's pointed to is constant, and if const appears to the right of the *, the pointer itself is constant. If const appears on both sizes, both are constants.
Using const with pointers has subtle aspects. Let's declare a pointer to a constant:
int year = 2012; const int *ptr = &year; *ptr = 2020; // not ok because ptr points to a const int
How about the following code:
const int year = 2012; int *p = &year; // not ok
C++ doesn't allow the last line for simple reason: if we can assign the address of year to p, then we can cheat and use p to modify the value of year. That doesn't make sense because year is declared as const. C++ prohibits us from assigning the address of a const to a non-const pointer.
Since STL iterators are modeled on pointers, an iterator behaves mush like a T* pointer. So, declaring an iterator as const is like declaring a pointer const. If we want an iterator that points to something that can't be altered (const T*), we want to use a const_iterator:
vector<int> v; vector<int>::const_iterator itc = v.begin(); *itc = 2012; // error: *itc is cost ++itc; // ok, itc is not const
How about T*const iterator:
vector<int> v; const vector<int>::iterator cit = v.begin; *cit = 2012; // ok ++cit; // error: cit is const
Making a function to display the array is simple. We pass the name of the array and the number of elements to the function. However, there are some implications. We need to guarantee that the display doesn't change the original array. In other words, we need to guard it from altering the values of array. That kind of protection comes automatically with ordinary parameters of the function because C++ passes them by value, and the function plays with a copy. But functions that use an array play with the original. To keep a function from accidentally modifying the contents of an array, we can use the keyword const:
void display_array(const int arr[], int sz);
This declaration says that the pointer arr points to constant data, which means that we can't use ar to alter the data.
A class designer indicates which member functions do not modify the class object by declaring them as const member functions. For example:
class Testing { public: void foo() const {} };
In that way, we can protect members of an object from being modified.
So, in the following example, we'll get an error. For VS, we get "Error: expression (val) must be a modifiable lvalue."
#include <iostream> using namespace std; class Testing { public: Testing(int n):val(n){} int getValue() const { return val; } void setValue(int n) const { val = n; } private: int val; }; int main() { Testing test1(10); return 0; }
Because, in the member function setValue() is trying to modify a member variable val though the function is declared as const. So, we should remove the const from the setValue() function.
There is another case which a member function appears against the const declaration:
#include <iostream> using namespace std; class Testing { public: Testing(int n):val(n){} void foo1() const { foo2(); } void foo2() {} private: int val; }; int main() { Testing test1(10); return 0; }
In this case, the member function foo2() is not doing anything. However, compiler thinks the foo2() is not safe because it does not have const declaration. In other words, compiler thinks that by calling non-constant function from const function, the code may try to change the value of the class object.
So, as a constant member function can't modify a data member of its class, a constant member function cannot make a call to a non-constant function.
However, there are exceptions to the rule:
- A constant member function can alter a static data member.
- If we qualify a data member with the mutable keyword, then even a constant member function can modify it.
The example below shows that a const member function can be overloaded with a non-const member function that has the same parameter list. In this case, the constness of the class object determines which of the two functions is invoked:
#include <iostream> using namespace std; class Testing { public: Testing(int n):val(n){} int getVal() const { cout << "getVal() const" << endl; return val; } int getVal() { cout << "getVal() non-const" << endl; return val; } private: int val; }; int main() { const Testing ctest(10); Testing test(20); ctest.getVal(); test.getVal(); return 0; }
Output is:
getVal() const getVal() non-const
Other exmaples using const related to returning object, see Object Returning.
#include <iostream> struct Foo { Foo() {} void go() { std::cout << "Foo" << std::endl; } }; struct Bar : public Foo { Bar() {} void go() { std::cout << "Bar" << std::endl; } }; int main(int argc, char** argv) { Bar b; const Foo f = b; f.go(); // 'Foo::go' : cannot convert 'this' pointer from 'const Foo' to 'Foo &' return 0; }
Many users bring up the idea of using C's keyword const as a means of declaring data to be in Program Space. Doing this would be an abuse of the intended meaning of the const keyword.
const is used to tell the compiler that the data is to be read-only. It is used to help make it easier for the compiler to make certain transformations, or to help the compiler check for incorrect usage of those variables.
For example, the const keyword is commonly used in many functions as a modifier on the parameter type. This tells the compiler that the function will only use the parameter as read-only and will not modify the contents of the parameter variable.
const was intended for uses such as this, not as a means to identify where the data should be stored. If it were used as a means to define data storage, then it loses its correct meaning (changes its semantics) in other situations such as in the function parameter example.
All header files should have #define guards to prevent multiple inclusion. The format of the symbol name should be <PROJECT>_<PATH>_<FILE>_H_.
To guarantee uniqueness, they should be based on the full path in a project's source tree. For example, the file foo/src/bar/baz.h in project foo should have the following guard:
#ifndef FOO_BAR_BAZ_H_ #define FOO_BAR_BAZ_H_ ... #endif // FOO_BAR_BAZ_H_
An enum is a very simple user-defined type, specifying its set of values as symbolic constants.
#include <iostream> enum Month { Jan = 1, Feb, Mar, Apr, May, June, Jul, Aug, Sep, Oct, Nov, Dec }; int main() { using namespace std; Month f = Feb; Month j = Jul; cout << "f = " << f << endl; // f = 2; // error: cannot convert from 'int' to 'Month' int jj = j; // allowed: can get the numeric value of a 'Month' Month jjj = Month(7); // Convering int to 'Month' cout << "jj = " << jj << ", jjj = " << jjj << endl; return 0; }
Output is:
f = 2 jj = 7, jjj = 7
Let's look at another usage example of enum:
#define NumArrays 10 class ArrayObj { private: int array[NumArrays]; }; int main() { ArrayObj a; return 0; }
Here, #define does its job. However, we can use const instead. const qualifier lets us specify the type explicitly as well as we can use scoping rules to limit the definition to particular functions or files. In other words, there's no way to create a class-specific constant using a #define, because #define doesn't respect scope.
class ArrayObj { private: static const int NumArrays = 5; int array[NumArrays]; };
We can also use enum for the array size:
class ArrayObj { private: enum {NumArrays = 5}; int array[NumArrays]; };
The following example defines days as enum, increment the days by one day, and then display them as strings by overloading + and <<.
#include <iostream> using namespace std; typedef enum days {SUN, MON, TUE, WED, THURS, FRI, SAT} days; days operator+(days d) { return static_cast<days> ((static_cast<int>(d) + 1) % 7); } ostream& operator<<(ostream& os, days d) { switch (d) { case SUN: os << "SUN"; break; case MON: os << "MON"; break; case TUE: os << "TUE"; break; case WED: os << "WED"; break; case THURS: os << "THURS"; break; case FRI: os << "FRI"; break; case SAT: os << "SAT"; break; } return os; } int main() { days d, a; d = WED ; a = +d; cout << d << " " << a << endl; d = SAT ; a = +d; cout << d << " " << a << endl; return 0; }
Output:
WED THURS SAT SUN
Adding explicit is a good practice for any constructor that accepts a single argument. It is used to prevent a specific constructor from being called implicitly when constructing an object. For example, without the explicit keyword, the following is valid C++ code:
Array a = 10;
This will call the Array single-argument constructor with the integer argument of 10:
Array::Array(int size) {}
This type of implicit behavior, however, can be confusing, and in most cases, unintended. As a further example of this kind of undesired implicit conversion, let's consider the following function:
void checkArraySize(const Array &array;, int size);
Without declaring the single-argument constructor of Array as explicit, we could call this function as
checkArray(10,10);
As another example, let's look at the following example which has a constructor that takes a single argument. It actually, defines a conversion from its argument type to its class:
class Complex { public: Complex(double) // This defines double-to-complex conversion Complex(double, double) }; ... Complex cmplx = 3.14 // OK: convert 3.14 to (3.14,0) Complex cmplx = Complex(1.0, 2.5);
But this kind of conversion may cause unexpected and undesirable effects as we see in the example below:
class Vector { int sz; double *elem; public: Vector(int s): sz(s) elem (new double[s]) { for(int i=0; i< s; ++i) elem[i] = 0; } ... };
The Vector has a constructor that takes an int, which implies that it defines a conversion from int to Vector:
class Vector { ... Vector(int); ... }; Vector v = 10 // makes a vector of 10 double ? v = 20; // assignes a new Vector of 20 double to v ?
This weakens the type safety of our code because now the compiler will not enforce the type of the first argument to be an explicit Array/Vector object in the above examples. As a result, there is the potential for the user to forget the correct order of arguments and pass them in the wrong order. This is why we chould always use the explicit keyword for any single argument constructors unless we know that we want to support implicit conversion.
So, for the 2nd example, we put explicit:
class Vector { ... explicit Vector(int); ... }; Vector v = 10 // error: no int-to-Vector,double> conversion v = 20; // error: no int-to-Vector,double> conversion Vector v(10) // OK
Though implementing a program as a set of functions is good from a software engineering standpoint, function calls involve execution-time overhead. So, C++ provides inline functions to help reduce function call overhead, especially for small functions. Placing the qualifier inline before a function's return type in the function definition tells the compiler to generate a copy of the function's code in place to avoid a function call.
The trade-off is that multiple copies of the function code are inserted in the program rather than there being a single copy of the function to which control is passed each time the function is called. The compiler can ignore the inline qualifier and typically does so for all but the smallest functions.
The complete definition of function should appear before it is used in the program. This is required so that the compiler knows how to expand a function call into its inlined code. For this reason, reusable inline functions are typically placed in header files, so that their definitions can be included in each source file that uses them.
In general, we provide declaration in our .h files and associated definition in our .cpp files. However, it's also possible to provide a definition for a method at the point where we declare it in a .h file:
class MyClass { public: void MyMethod() { } };
This implicitly requests the compiler to inline the MyMethod() member function at all places where it is called. In terms of API design, this is a bad practice since it exposes the code for how the method has been implemented and directly inlines the code into our clients' programs.
There are exceptions to this rule to support templates and intentional use of inline.
- Pros
Inlining a function can generate more efficient object code, as long as the inlined function is small. Feel free to inline accessors and mutators, and other short, performance-critical functions. - Cons
Overuse of inlining can actually make programs slower. Depending on a function's size, inlining it can cause the code size to increase or decrease. Inlining a very small accessor function will usually decrease code size while inlining a very large function can dramatically increase code size. On modern processors smaller code usually runs faster due to better use of the instruction cache. - Decision
A decent rule of thumb is to not inline a function if it is more than 10 lines long. Beware of destructors, which are often longer than they appear because of implicit member- and base-destructor calls!
Another useful rule of thumb: it's typically not cost effective to inline functions with loops or switch statements (unless, in the common case, the loop or switch statement is never executed).
It is important to know that functions are not always inlined even if they are declared as such; for example, virtual and recursive functions are not normally inlined. Usually recursive functions should not be inline. The main reason for making a virtual function inline is to place its definition in the class, either for convenience or to document its behavior, e.g., for accessors and mutators.
To allow a class data member to be modified even though it is the data member of a const object, we can declare the data member as mutable. A mutable member is a member that is never const, even when it is the data member of a const object. A mutable member can always be updated, even in a const member function.
struct account { char name[50]; mutable int id; }; const account ac = {"Bush", 0, ....}; strcpy(ac.name, "Obama"} // not allowed ac.id++; // allowed
The following example has an error because it tries to modify a variable which in a const member function:
#include <iostream> #include <cstring> class MyText { public: std::size_t getLength() const; private: char * ptrText; std::size_t txtLen; }; std::size_t MyText::getLength() const { // error: l-value specifies const object // cannot assign to txtLen because it is in a const member function txtLen = std::strlen(ptrText); return txtLen; }
We can solve the problem. mutable frees non-static data members from the const constraints:
#include <iostream> #include <cstring> class MyText { public: std::size_t getLength() const; private: char * ptrText; mutable std::size_t txtLen; }; std::size_t MyText::getLength() const { txtLen = std::strlen(ptrText); // ok return txtLen; }
How can we make the code work by making changes only inside class C
#include <iostream> class C { private: int num; public: C(int a) : num(a) {} int get_val() const; }; //changes are not allowed in below code int C::get_val() const { num++; return num; } int main() { C obj(29); std::cout << obj.get_val() << std::endl; }
Ans: mutable int num;
As programming projects grow large, the potential for name conflicts increases. The language mechanism for organizing classes, functions, data, and types into an identifiable and named part of a program without defining a type is a namespace. The C++ standard provides namespace which allows us to have greater control over the scope of names.
Note that a namespace scope does not end with a semicolon(;).
The following code uses namespace to create two namespaces, Stock, and Market:
namespace Stock { double penny; void order(); int amount; struct Option { ... }; } namesapce Market { double dollar; void order(); int amount; struct Purchase { ... }; }
The names in any one namespace don't conflict with names in another namespace. So, the order in Stock is not confused with the order in Market
Namespaces can be located at the global level or inside other namespaces, but they cannot be in a block. Therefore, a name declared in a namespace has external linkage by default.
Namespaces are open or they can be discontiguous. In other words, we can add names to existing namespaces. For instance, we can add another name to the existing list of names in Stock:
namespace Stock { string getCompanyName(); }
The original Stock namespace provides a prototypes for order() function. We can provide the code for the function later in the file or in another file by using the Stock namespace again:
namespace Stock { void order() { ... } }
How do we access names in a given namespace?
We use the scope-resolution operator (::), to qualify a name with its namespace:
Stock::amount = 200; Market::Purchase p; Market::order();
Just variable name, such as penny is called unqualified name, which a name with the namespace, as in Market::dollar is called qualified name.
As a quick summary:
If we want to reference "j" in "main()", how do we do that?
namespace { int j; } int main() { // ?? return 0; }
Answer:
int i = ::j;
So, it looks like this when we have j both in global and local scope:
#include <iostream> namespace { int j; } int main() { ::j = 911; int j = 800; int n1 = ::j; int n2 = j; std::cout << "n1 = " << n1 << std::endl; std::cout << "n2 = " << n2 << std::endl; return 0; }
Output:
n1 = 911 n2 = 800
The ::j appears to be defined at global scope and it can be handled as such. In other words, it is declared outside any class, function, or namespace. The global namespace is implicitly declared and exists in every program. Each file that defines variables at global scope adds those names to the global namespace. To refer to the member in the global namespace, we use scope operator(::). However, the ::j is not actually defined at global scope. More specifically, it is declared using unnamed namespace. So, unlike the variable in the global namespace, it is local to a particular file and never space multiple files.
The register keyword is a hint to the compiler that we want it to provide fast access to the variable, perhaps by using a CPU register instead of the stack to handle a particular variable. The CPU can access a value in one of its registers more quickly than it can access memory in the stack. Some compilers may ignore the hint and use register allocation algorithms to figure out the best candidates to be placed within the available machine registers. Because the compiler is aware of the machine architecture on which the program is run, it is often able to make a more informed decision when selecting the content of machine registers.
Usually, automatic objects used heavily within a function can be declared with the keyword register. If possible, the compiler will load the object into a machine register. If it cannot, the object remains in memory.
To declare a register variable, we preface the type with the keyword register:
register int heavy_use;
Array indexes and pointers occurring within a loop are good candidates for register objects.
for (register int i = 0; i < sz ; i++) ... for (register int *ip = array; p < arraySize ; p++)
If a variable is stored in a register, it doesn't have a memory address. So, we can't apply the address operator to a register variable. Therefore, in the following code, it's okay to take the address of the variable xStack but not of the register variable xRegister:
void f(int *); int main() { int xStack; int register xRegister; f(&xStack;) // ok f(&xRegister;) // not ok ... }
The keyword struct introduces a structure declaration, which is a list of declarations enclosed in braces.
struct structure_name { type1 member1; type2 member2; } object_name;
Specific example is like this:
struct data { int idata; float fdata; } myData; myData.idata = 10; myData.fdata = 3.14;
Once used like above, the data can represent for the declaration {...}, and it can be used later in definition of instances of the structure. For example, it can be used like this:
struct data yourData;
It defines a variable yourData which is a structure of type struct data.
We can also define array of structures with initializer:
#include <stdio.h> struct data { int idata; float fdata; } myData[] = { {10, 3.14}, {20, 0.314}, {30, 0.0314} }; int main() { printf("%d,%f\n", myData[0].idata,myData[0].fdata); printf("%d,%f\n", myData[1].idata,myData[1].fdata); printf("%d,%f\n", myData[2].idata,myData[2].fdata); printf("%d\n", sizeof data); // 8 printf("%d\n", sizeof myData); // 24 printf("%d\n", sizeof *myData); // 8 return 0; }
The number of entries in the array myData[] will be computed if initializers are there and the [] is left empty as in the above example.
We can also calculate the size of myData[] array using:
sizeof myData / sizeof (struct data)
or
sizeof myData / sizeof myData[0]
The 2nd one is better because it does not need to be changed if the type changes. For more on the size of struct, see Size of struct
The struct is very useful when we build linked list:
struct list { int data; struct list *next; };
The recursive declaration looks illegal because it's referring itself. But it's not. It's not containing an instance of itself, but
struct list *next;
declares next to be a pointer to a list, not a list itself.
For more on the size of struct, visit the size of the struct.
Suppose we have the struct as below:
typedef struct t { int i; float f; t* ptr; } T;
How can we initialize the array of that struct?
Here is the answer:
T myStruct[10] = {{0}};
The example below is a sort of quiz for switch related to break and continue. We should be able to guess the output from the code:
#include <iostream> using namespace std; int main() { for (int i = 0; i < 5; ++i) { switch (i) { case 0 : cout << "0"; case 1 : cout << "1"; continue; case 2 : cout << "2"; break; case 3 : cout << "3"; break; default : cout << "d"; break; } cout << "*"; } return 0; }
Output should look like this:
0112*3*d*
At i = 0, prints out 0, continue to case 1, print 1, then next index of the loop
at i = 1, skip case 0, go to case 1 and print 1, then next index of the loop
at i = 2, skip case 0 and 1, go to 2, print 2, break, print *
at i = 3, skip case 0-2, at case 3, print 3, break print *
at i = 4, skip call cases, at default, print d, print *.
We can define a new name for an existing type. We use typedef to create an alias:
typedef typeName aliasName;
So, we can make byte_pointer an alias for char *:
typedef char* byte_pointer;
Or we can create shorter names for types with longer names:
typedef unsigned short int ushort;
It defines ushort as another name for the type unsigned short int.
As an another example:
struct node { int data; node *next; }; typedef node node_t;
or
typedef struct node { int data; node *next; } node_t;
But typedef declaration does not create a new type. It just adds a new name for existing type. The primary reason of using typedef is to parameterize a code against portability issues. So, by just changing typedefs, we can minimize the change in our source code.
There is another issue regarding the difference between C and C++ in using struct tagged namespace:
In C, we'll get an error if we do:
struct Foo { ... }; Foo x;while we do not get any error in C++.
So, we should use the following instead:
struct Foo { ... }; struct Foo x;
Why? Here is an explanation.
So, I usually use typedef to avoid this subtle difference:
typedef struct Foo { ... } Foo;
C++ provides two mechanisms to qualify names:
- using declaration lets us to make particular identifiers available.
using Stock::order; // a using declaration
- using directives makes the entire namespace accessible.
using namespace Stock; // make all the names in Stock available
But we should not use the using keyword in the global scope of our public headers because it would against the purpose of using namespaces in the first place. If we want to reference symbols in another namespace in our header, we should use fully qualified name, i.e., std::string.
The volatile keyword indicates that the value in a memory location can be altered in ways unknown to the compiler or have other unknown side effects (e.g. modification via a signal interrupt, hardware register, or memory mapped I/O) even though nothing in the program code modifies the contents. In other words, volatile informs the compiler that the value of the variable can change from the outside, without any update done by the code.
As an example, we can think of a memory-mapped register representing a DIP-switch input:
while register is read and saved into a general-purpose register,
our program may keep reading the same value, even if hardware has
changed.
The intent of volatile keyword is to improve the optimization of compilers.
For more on optimization with volatile keywords, visit Volatile Optimization.
In that optimization, compilers, can cache a value in a register if it's used several times with the same value, under the assumption the variable doesn't change during those uses. If we don't declare a variable as volatile, then the compiler may make the optimization. If we do declare a variable as volatile, we're telling the compiler not to make the optimization of the code referring to the object.
In other words, volatile just tells the compiler to reload variables from memory before using them and store them back to memory after they have been modified.
Declaring a variable as volatile is more applicable to systems-level programming rather than normal applications-level programming.
More on volatile:
- Can a parameter be both const and volatile?
Yes.
A read-only status register is an example.
Also, embedded systems can have many kinds of input peripherals such as free-running timers and keypad interfaces. They must be declared "const volatile", because they both- change value outside by means outside our C program, and also
- our C program should not write values to them (it makes no sense to write a value to a 10-key keypad).
It is const because our C program must not try to modify it. - Can a pointer be volatile?
An Interrupt Service Routine (ISR) can modify a pointer. - Check the following square code:
int square(volatile int *p) { return *p * *p; }
Will the code always be working?
I found this code from the Web but not sure about the answer. Answer from the site: It may be not.
That's because the compiler would produce the internal code like this:int square(volatile int *p) { int a = *p; int b = *p return a*b; }
In other words, we may be end up multiplying different numbers because it's volatile and could be changed unexpectedly. So, the site suggested the following code:int square(volatile int *p) { int a = *p; return a*a; }
The essense of embedded programming is that it requires communications with the outside world. So, both input and output devices needs the volatile keyword.
There are at least 3 types of optimizations that volatile turns off:
- Read optimizations
Without volatile, C compilers assume that once the program reads a variable into a register, it doesn't need to re-read that variable every time the source code mentions it, but can use the cached value in the register. This works great with normal values in ROM and RAM, but fails miserably with input peripherals. The outside world, and internal timers and counters, frequently change, making the cached value stale and irrelevant. - Write optimizations
Without volatile, C compilers assume that it doesn't matter what order writes occur to different variables, and that only the last write to a particular variable really matters. This works great with normal values in RAM, but fails miserably with typical output peripherals. Sending "turn left 90, go forward 10, turn left 90, go forward 10" out the serial port is completely different than "optimizing" it to send "0" out the serial port. - Instruction reordering
Without volatile, C compilers assume that they can reorder instructions. The compiler may decide to change the order in which variables are assigned to make better use of registers. This may fail miserably with IO peripherals where you, for example, write to one location to acquire a sample, then read that sample from a different location. Reordering these instructions would mean the old/stale/undefined sample is 'read', then the peripheral is told to acquire a new sample (which is ignored).
References on volatile:
- "Empirical data suggests that incorrect optimization of volatile objects is one of the most common defects in C optimizers".
- Placing C variables at specific addresses to access memory-mapped peripherals.
Ph.D. / Golden Gate Ave, San Francisco / Seoul National Univ / Carnegie Mellon / UC Berkeley / DevOps / Deep Learning / Visualization