This chapter describes several different programming options for the BeagleBone, including scripted and compiled languages. An LED flashing example is provided in all of the languages so that you can investigate each language’s structure and syntax. The advantages and disadvantages of each language are discussed along with example uses. The chapter then focuses on the C/C++ programming languages, describing the principles of these languages, and why object‐oriented programming (OOP) is appropriate and necessary for the development of scalable embedded systems applications. Finally, the chapter details how you can interface directly to the Linux kernel using the GNU C Library. A single chapter can only touch the surface of programming languages, but this one is focused on programming the BeagleBone. I have made my module notes about OOP available at ee402.eeng.dcu.ie
to support this chapter.
Equipment Required for This Chapter:
As discussed in Chapter 3, embedded Linux is essentially “Linux on an embedded system.” If your favorite programming language is available under Linux, then it is also likely to be available for the BBB. So, is your favorite language suitable for programming the BBB? That depends on what you intend to do with the board. Are you interfacing to electronics devices/modules? Do you plan to write rich user interfaces? Are you planning to write a device driver for Linux? Is performance very important, or are you developing an early pre‐prototype? Each of the answers to these questions will impact your decision regarding which language you should use. In this chapter, you are introduced to several different languages, and the advantages and disadvantages of each category of language are outlined. As you read through the chapter, try to avoid focusing on a favorite language, but instead use the correct language for the job at hand.
How does programming on embedded systems compare to programming on desktop computers? Here are some points to consider:
For the upcoming discussion, it is assumed that you are planning to do some type of physical computing—that is, interfacing to the different input or outputs on the BBB. Therefore, the example that is used to describe the structure and syntax of the different languages is a simple interfacing example. Before looking at the languages themselves, we will begin with a brief performance evaluation of different languages running on the BBB, in order to put the following discussions in context.
Which language is the fastest on the BBB? Well, that is an incredibly emotive and difficult question to answer. Different languages perform better on different benchmarks and different tasks. In addition, a program written in a particular language can be optimized for that language to the point that it is barely recognizable as the original code. Nor is speed of execution always an important factor; you may be more concerned with memory usage, the portability of the code, or the ability to quickly apply changes.
However, if you are planning to develop high‐speed or real‐time number‐crunching applications, then performance may be a key factor in your choice of programming language. In addition, if you are setting out to learn a new language, and you may possibly be developing algorithmically rich programs in the future, then it may be useful to keep performance in mind.
A simple test has been put in place on the BBB to determine the performance of the languages discussed in this chapter. The test uses the n‐body benchmark (gravitational interaction of planets in the solar system) code from benchmarksgame.alioth.debian.org
. The code uses the exact same algorithm for all languages and the BBB is running in the exact same state in all cases. The test uses 5 million iterations of the algorithm to ensure that the script used for timing does not have to be highly accurate. All of the programs gave the same correct result, indicating that they all ran correctly and to completion. The test is available in the book’s Git repository in the directory chp05/performance/
. Note that you must have installed Java on the BBB in order to run all of the tests. That is discussed later in this chapter. Use the following call to execute the test:
/chp05/performance$ ./run
Running the Tests:
The C/C++ Code Example
-0.169075164 -0.169083134
It took 33 seconds to run the C/C++ test ...
Finished Running the Benchmarks
The results of the tests are displayed in Table 5-1. In the first column you can see the results for the BBB, running at its top processor frequency of 1 GHz. For this number‐crunching application, C++ performs the task in the shortest time, taking 33 seconds to complete. This time has been weighted as one unit. Therefore, Java takes 1.18 times longer to complete the same task, Node.js (for BoneScript) 2.36 times longer, Perl 27.8 times longer, and Python 32.2 times longer. The processing durations are provided in parentheses. As you move across the columns you can see that this performance is relatively consistent, even as the processor frequency is adjusted (discussed in the next section) or a desktop i7 64‐bit processor is used.
Table 5-1 Numerical Computation Time for 5,000,000 Iterations of the n‐Body Algorithm on a BBB Debian Hard‐float Image
VALUE | BBB@1GHZ | BBB@800MHZ | BBB@300MHZ | DEBIAN 64‐BIT I7 PC |
C++ | 1.00× (33s) | 1.00× (42s) | 1.00× (114s) | 1.00× (0.821s) |
C++11 | 1.15× (38s) | 1.16× (48s) | 1.16× (132s) | 0.91× (0.746s) |
Java | 1.18× (39s) | 1.16× (49s) | 1.19× (134s) | 1.24× (1.023s) |
Node.js | 2.36× (78s) | 2.21× (93s) | 2.19× (248s) | 5.78× (4.523s) |
Perl | 27.8× (917s) | 27.9× (1170s) | 28.7× (3244s) | 75.64× (62.1s) |
Python | 32.2× (1063s) | 30.8× (1294s) | 31.7× (3586s) | 83.92× (68.9s) |
All of the programs use between 98% and 99% of the CPU while they are executing. In addition, the C++ program used ∼0.1% of memory, the Java and Node.js programs used ∼2.3% of memory, and the Python application used ∼0.8% of memory while executing. The relative performance of Java and Node.js is impressive given that code is compiled dynamically (“just‐in‐time”), which is discussed later in this chapter. Any dynamic compilation latency is included in the timings, as the test script includes the following Bash script code to calculate the execution duration of each program:
Duration="5000000"
echo -e "
The C/C++ Code Example"
T="$(date +%s%N)"
./n-body $Duration
T="$(($(date +%s%N)-T))"
T=$((T/1000000))
echo "It took ${T} milliseconds to run the C/C++ test"
The C++11 code is the version of the C++ programming language that was approved in mid‐2011 (needs gcc 4.7+). This is discussed again in Chapter 7. The program contains optimizations that are specific to this release of C++ and interestingly, while this version performs better on the desktop computer, it underperforms on the BBB. The Java program uses the +AgressiveOpts
flag to enable performance optimization, and it was used because it did not involve modifying the source code.
The results for Python are particularly poor due to the algorithmic nature of the problem. However, the benchmarks at (debian.org, 2013),1 indicate that the range will be 9 to 100 times slower than the optimized C++ code for general processing to algorithm‐rich code, respectively. If you are very comfortable with Python and you would like to improve upon its performance, then you can investigate Cython, which is a Python compiler that automatically removes the dynamic typing capability and enables you to generate C code directly from your Python code. On the Debian image you can enter apt‐get install cython
and apt‐get install python‐dev
.
The final column provides the results for the same code running on a desktop computer virtual machine (a 64‐bit Ubuntu 3.11.0 kernel in a VirtualBox VM, allocated one thread on one core of an Intel [email protected] GHz processor and 10 GB of RAM, with no processing cap). You can see that the relative performance of the applications is broadly in line, but also note that the C++ program runs 40 times faster on the single i7 thread than it does on the BBB at 1 GHz. Hopefully that will help you frame your expectations with respect to the type of numerical processing that is possible on a standard BBB, particularly when investigating computationally expensive applications like signal processing and computer vision.
As previously discussed, this is only one numerically-oriented benchmark test, but it is somewhat indicative of the type of performance you should expect from each language. There have been many studies on the performance of languages; however, a recent and well‐specified analysis by Hundt (2011) has found that in terms of performance, “C++ wins out by a large margin. However, it also required the most extensive tuning efforts, many of which were done at a level of sophistication that would not be available to the average programmer” (Hundt, 2011).2
In the previous section, the clock frequency of the BBB was adjusted dynamically at run time. The BBB has various governors that can be used to profile the performance/power usage ratio. For example, if you were building a battery‐powered BBB application that has low processing requirements, you could reduce the clock frequency to conserve power. You can find out information about the current state of the BBB by typing the following:
root@beaglebone:~# cpufreq‐info
... available frequency steps: 300 MHz, 600 MHz, 800 MHz, 1000 MHz
governors: conservative, ondemand, userspace, powersave, performance
current CPU frequency is 1000 MHz (asserted by call to hardware).
cpufreq stats: 300 MHz:95.04%, 600 MHz:0.15%, 800 MHz:0.05%,
1000 MHz:4.76% (159)
You can see that different governors are available, with the profile names conservative
, ondemand
, userspace
, powersave
, and performance
. To enable one of these governors type the following:
root@beaglebone:~# cpufreq‐set ‐g performance
root@beaglebone:~# cpufreq‐info
current policy: frequency should be within 300 MHz and 1000 MHz.
The governor "performance" may decide which speed to use
root@beaglebone:~# cpufreq‐set ‐f 800MHz
root@beaglebone:~# cpufreq‐info
current CPU frequency is 800 MHz (asserted by call to hardware)
These commands need to be executed with root privileges. If these tools are not installed on your BBB, you can install the cpufrequtils
package. The default governor is ondemand
, which will dynamically switch CPU frequencies if the BBB reaches 95% of CPU load.
A scripting language is a computer programming language that is used to specify script files, which are interpreted directly by a run‐time environment to perform tasks. Many scripting languages are available, such as Bash, Perl, and Python, and these can be used to automate the execution of tasks on the BBB, such as system administration, interaction, and even interfacing to electronic components.
Which scripting language should you choose for the BBB? There are many strong opinions and it is a difficult topic, as Linux users tend to have a favorite scripting language; however, you should choose the scripting language with features that suit the task at hand. For example:
These three scripting languages are available pre‐configured on the BBB standard Debian image. It would be very useful to have some knowledge of all of these scripting languages, as you may find third‐party tools or libraries that make your current project very straightforward. This section provides a brief overview of each of these languages, including a concise segment of code that performs the same function in each language. It finishes with a discussion about the advantages and disadvantages of scripting languages in general.
In Chapter 2 an approach is described for changing the state of the on‐board LEDs using Linux shell commands. It is possible to turn an LED on or off, and even make it flash. For example, you can use:
root@beaglebone:/sys/class/leds/beaglebone:green:usr3# echo none > trigger
root@beaglebone:/sys/class/leds/beaglebone:green:usr3# echo 1 > brightness
root@beaglebone:/sys/class/leds/beaglebone:green:usr3# echo 0 > brightness
to turn a user LED on and off. This section examines how it is possible to do the same tasks but in a structured programmatic form.
Bash scripts are a great choice for short scripts that do not require advanced programming structures, and that is exactly the application to be developed here. The first program leverages the Linux console commands such as echo
and cat
to create the concise script in Listing 5‐1 that enables you to choose, using command‐line arguments, whether you wish to turn USR3 LED on or off, or place it in a flashing mode. For example, using this script by calling ./bashLED on
would turn the USR3 LED on. It also provides you with the trigger status information:
The script is available in the directory /chp05/bashLED
. If you entered the script manually using the nano
editor, then the file needs to have the executable flag set before it can be executed (the Git repository retains executable flags). Therefore, to allow all users to execute this script, use the following:
/chp05/bashLED$ chmod ugo+x bashLED
What is happening within this script? First, all of these command scripts begin with a sha‐bang #!
followed by the name and location of the interpreter to be used, so #!/bin/bash
in this case. The file is just a regular text file, but the sha‐bang is a magic‐number code to inform the OS that the file is an executable. Next, the script defines the path to the LED for which you wish to change state using the variable LED3_PATH
. This allows the default value to be easily altered if you wish to use a different user LED or path.
The script contains a function called removeTrigger
, mainly to demonstrate how functions are structured within Bash scripting. This function is called later in the script. Each if
is terminated by a fi
. The ;
after the if
statement terminates that statement and allows the statement then
to be placed on the same line. The elif
keyword means else if
, which allows you to have multiple comparisons within the one if
block. The newline character
terminates statements.
The first if
statement confirms that the number of arguments passed to the script ($#
) is not equal to 1
. Remember that the correct way to call this script is of the form ./bashLED on
. Therefore, on
will be the first user argument that is passed ($1
) and there will be one argument in total. If there are no arguments, then the correct usage will be displayed and the script will exit with the return code 2
. This value is consistent with Linux system commands, where an exit value of 2 indicates incorrect usage. Success is indicated by a return value of 0
, so any other non‐zero return value generally indicates the failure of a script.
If the argument that was passed is on
then the code displays a message; calls the removeTrigger
function; and writes the string “1” to the brightness
file in the LED3 /sys
directory. The remaining functions modify the USR3 LED values in the same way as described in Chapter 2. You can execute the script as follows:
/chp05/bashLED$ ./bashLED
There are no arguments. Usage is:
bashLED Command, where command is one of
on, off, flash or status e.g., bashLED on
/chp05/bashLED$ ./bashLED status
The LED Command that was passed is: status
[none] rc-feedback mmc0 mmc1 timer oneshot heartbeat backlight gpio cpu0
/chp05/bashLED$ sudo ./bashLED on
The LED Command that was passed is: on
Turning the LED on
/chp05/bashLED$ sudo ./bashLED flash
/chp05/bashLED$ sudo ./bashLED off
Notice that the script was prefixed by sudo
when it was called. This is required, as the script was executed by the molloyd
user account. The LED sysfs
directory is owned by the root
account and regular users do not have write access to change the state. For security reasons, you cannot use the setuid bit on a script to set it to execute as root. If users had write access to this script and its setuid bit was set as root, then they could inject any command that they wished into the script and would have de facto superuser access to the system.
A short script restoreDefaultLEDs
is available in the repository chp05
directory. It returns the BBB LEDs back to their default states. For a comprehensive online guide to Bash scripting, please see Mendel Cooper’s “Advanced Bash‐Scripting Guide”: www.tldp.org/LDP/abs/html/
.
Perl is a feature‐rich scripting language that provides you with access to a huge library of reusable modules and portability to other OSs (including Windows). Perl is best known for its text processing and regular expressions modules. In the late 1990s it was a very popular language for server‐side scripting for the dynamic generation of web pages. Later it was superseded by technologies such as Java servlets, Java Server Pages (JSP), and PHP. The language has evolved since its birth in the 1980s and now includes support for the OOP paradigm. Perl 5 (v14+) is installed by default on the BBB Debian Linux image. Listing 5‐2 provides a segment of a Perl example that has the same structure as the Bash script, so it is not necessary to discuss it in detail. Apart from the syntax, very little has actually changed in the translation to Perl.
A few small points are worth noting in the code: The “<
” or “>
” sign on the filename indicates whether the file is being opened for read or write access, respectively; the arguments are passed as $ARGV[0...n]
and the number of arguments is available as the value $#ARGV
; a file open, followed by a read or write, and a file close are necessary to write the values to /sys
; and the arguments are passed to the subroutine writeLED3
and these are received as the values $_[0]
and $_[1]
, which is not the most beautiful programming syntax, but it works perfectly well. To execute this code, simply type sudo ./perlLED.pl on
, as the sha‐bang identifies the Perl interpreter. You could also execute it by typing perl perlLED.pl status
.
For a good resource about getting started with installing and using Perl 5, see the guide “Learning Perl” at learn.perl.org
.
Python is a dynamic and strongly typed OOP language that was designed to be easy to learn and understand. Dynamic typing means that you do not have to associate a type (e.g., integer, character, string) with a variable; rather, the value of the variable “remembers” its own type. Therefore, if you were to create a variable x=5
, the variable x
would behave as an integer; but if you subsequently assign it using x=“test”
, it would then behave like a string. Statically typed languages such as C/C++ or Java would not allow the re‐definition of a variable in this way (within the same scope). Strongly typed means that the conversion of a variable from one type to another requires an explicit conversion. The advantages of object‐oriented programming structures are discussed later in this chapter. Python is installed by default on the Debian BBB image. The Python example to flash the LED is provided in Listing 5‐3.
The formatting of this code is important—in fact, Python enforces the layout of your code by making indentation a structural element. For example, after the line “if len(sys.argv)!=2:
” the next few lines are “tabbed” in. If you did not tab in one of the lines—for example, the sys.exit(2)
line—then it would not be part of the conditional if
statement and the code would always exit at this point in the program. To execute this example, in the pythonLED
directory enter the following:
/chp05/pythonLED$ sudo ./pythonLED.py flash
Flashing the LED
/chp05/pythonLED$ ./pythonLED.py status
Getting the LED trigger status
none rc-feedback mmc0 mmc1 [timer] oneshot heartbeat backlight gpio cpu0
Python is very popular on the BBB, and most of the other books currently available on the BBB use Python as the core programming language. A list of books is provided at the end of this chapter. Compared to Perl, the syntax is very straightforward and there is not too much to explain about the preceding code, having explained the Bash and Perl versions.
To conclude this discussion of scripting, there are several strong choices for applications on the BBB. Table 5-2 lists some of the key advantages and disadvantages of command scripting on the BBB, when considered in the context of the compiled languages which are discussed shortly.
Table 5-2 Advantages and Disadvantages of Command Scripting on the BBB
ADVANTAGES | DISADVANTAGES |
Perfect for automating Linux system administration tasks that require calls to Linux commands. | Performance is poor for complex numerical or algorithmic tasks. |
Easy to modify and adapt to changes. Source code is always present and complex toolchains (see Chapter 7) are not required in order to make modifications. Generally, nano is the only tool that you need. |
Generally, relatively poor/slow programming support for data structures, graphical user interfaces, sockets, threads, etc. |
Generally, straightforward programming syntax and structure that is reasonably easy to learn when compared to languages like C++ and Java. | Generally, poor support for complex applications involving multiple, user‐developed modules or components (Python and Perl do support OOP). |
Generally, quick turnaround in coding solutions by occasional programmers. | Code is in the open. Direct access to view your code can be an intellectual property or a security concern. |
Lack of development tools (e.g., refactoring). |
With the interpreted languages just discussed, the source code text file is “executed” by the user passing it to a run‐time interpreter, which then translates or executes each line of code. JavaScript and Java have different life cycles and are quite distinct languages.
As discussed in Chapter 2, in the section “Node.js, Cloud9, and BoneScript,” Node.js is JavaScript that is run on the server side. JavaScript is an interpreted language by design; however, thanks to the V8 engine that was developed by Google for their Chrome web browser, Node.js actually compiles JavaScript into native machine instructions as it is loaded by the engine. This is called just‐in‐time (JIT) compilation or dynamic translation. As demonstrated at the beginning of this chapter, Node.js’s performance for the numerical computation tasks is very impressive for an interpreted language.
Listing 5-4 shows the same LED code example written using JavaScript and executed by calling the nodejs
executable:
The code is available in the /chp05/nodejsLED
/ directory and it can be executed by typing nodejs nodejsLED.js [option]
. The code has been structured in the same way as the previous examples and there are not too many syntactical differences; however, there is one major difference between Node.js and other languages: functions are called asynchronously. Up to this point, all of the languages discussed followed a sequential‐execution mode. Therefore, when a function is called, the program counter (also known as the instruction pointer) enters that function and does not reemerge until the function is complete. Consider, for example, code like this:
functionA();
functionB();
The functionA()
is called and functionB()
will not be called until functionA()
is fully complete. This is not the case in Node.js! In Node.js, functionA()
is called first and then Node.js continues executing the subsequent code, including entering functionB()
, while the code in functionA()
is still being executed. This presents a serious difficulty for the current application, with this segment of code in particular:
case 'flash':
console.log("Making the LED Flash");
writeLED("/trigger", "timer", LED3_PATH);
writeLED("/delay_on", "50", LED3_PATH);
writeLED("/delay_off", "50", LED3_PATH);
break;
The first call to writeLED()
sets up the sysfs
file system (as described in Chapter 2) to now contain new delay_on
and delay_off
file entries. However, due to the asynchronous nature of the calls, the first writeLED()
call has not finished setting up the file system before the next two writeLED()
calls are performed. This means that the delay_on
and delay_off
file system entries are not found, and the code to write to them fails. You should test this by changing the call near the top of the program from fs.writeFileSync(...)
to fs.writeFile(...)
.
To combat this issue you can synchronize (prevent threads from being interrupted) the block of code where the three writeLED()
functions are called, ensuring that the functions are called sequentially. Alternatively, as shown in this code example, you can use a special version of the Node.js writeFile()
function called writeFileSync()
to ensure that the first function call to modify the file system blocks the other writeFileSync()
calls from taking place.
Node.js allows asynchronous calls because they help ensure that your code is “lively.” For example, if you performed a database query, your code may be able to do something else useful while awaiting the result. When the result is available, a callback function is executed in order to process the received data. This asynchronous structure is perfect for Internet‐attached applications, where posts and requests are being made of websites and web services, and it is not clear when a response will be received (if at all). Node.js has an event loop that manages all the asynchronous calls, creating threads for each call as required, and ensuring that the callback functions are executed when an asynchronous call completes its assigned tasks.
Up to this point in the chapter, interpreted languages are examined, meaning the source code file (a text file) is executed using an interpreter or dynamic translator at run time. Importantly, the code exists in source code form, right up to the point when it is executed using the interpreter.
With traditional compiled languages, the source code (a text file) is translated directly into machine code for a particular platform using a set of tools, which we will call a compiler for the moment. The translation happens when the code is being developed; once compiled, the code can be executed without needing any additional run‐time tools.
Java is a hybrid language: You write your Java code in a source file, e.g., example.java
, which is a regular text file. The Java compiler (javac
) compiles and translates this source code into machine code instructions (called bytecodes) for a Java virtual machine (VM). Regular compiled code is not portable between hardware architectures, but bytecode files (.class
files) can be executed on any platform that has an implementation of the Java VM. Originally, the Java VM interpreted the bytecode files at run time; however, more recently, dynamic translation is employed by the VM to convert the bytecodes into native machine instructions at run time.
The key advantage of this life cycle is that the compiled bytecode is portable between platforms; and because it is compiled to a generic machine instruction code, the dynamic translation to “real” machine code is very efficient. The downside of this structure when compared to compiled languages is that the VM adds overhead to the execution of the final executable.
Presently, there is no version of the Java Development Kit for embedded Linux, which prevents you from compiling code directly on the BBB. In addition, the Java Runtime Environment (JRE), which provides the Java virtual machine (JVM), is not installed on the BBB by default.
There is a full guide to installing the JRE on the BBB and the JDK on a desktop Linux machine at the chapter web page. Listing 5‐5 provides a source code example that is also available in the GitHub repository in bytecode form.
Early versions of Java suffered from poor computational performance; however, more recent versions take advantage of dynamic translation at run time (just‐in‐time, or JIT, compilation) and, as demonstrated at the start of this chapter, the performance was less than 20% slower (including dynamic translation) than that of the natively compiled C++ code, with only a minor additional memory overhead. Table 5-3 lists some of the advantages and disadvantages of using Java for development on the BBB.
Table 5-3 Advantages and Disadvantages of Java on the BBB
ADVANTAGES | DISADVANTAGES |
Code is portable. Code compiled on the PC can be executed on the BBB or another embedded Linux platform. | You cannot currently build your Java applications directly on the BBB. |
There is a vast and extensive library of code available that can be fully integrated in your project. | Executing as root is slightly difficult due to required environment variables. |
Full OOP support. | It’s not suitable for scripting. |
Can be used for user‐interface application development on the BBB when it is attached to a display. | Computational performance is very respectable, but slower than optimized C/C++ programs. Slightly heavier on memory. |
Strong support for multi‐threading. | Strictly typed and no unsigned integer types. |
Has automatic memory allocation and de‐allocation using a garbage collector, removing memory leak concerns. | Royalty payment required if deployed to a platform that “involves or controls hardware” (Oracle, 2014).3 |
Sandboxed applications do not have access to system memory, registers or system calls (except through /proc) or JNI (Java Native Interface). |
To execute a Java application under Debian/Ubuntu, where it needs access to the /sys
directory, you need the application to run with root access. Unfortunately, because you need to pass the bytecode (.class
) file to the Java VM, you have to call sudo and create a temporary shell of the form sudo sh ‐c 'java myClass'
. However, because you set the environment variables for PATH
and JAVA_HOME
within the user account, these will not be present in the root account. For example:
/chp05/javaLED$ export TEST=test
/chp05/javaLED$ sudo sh ‐c 'echo $TEST'
/chp05/javaLED$ sudo ‐E sh ‐c 'echo $TEST'
test
In the first case, there was no output, as the TEST
environment variable is not passed to the root shell; however, the addition of ‐E
means that the variable is passed. Unfortunately, this will not work for PATH
, because to prevent Trojan horse application attacks, the root PATH
is set in the /etc/sudoers
file. Therefore, to execute your Java application, you have to modify the sudoers
configuration file using sudo visudo
and add the following modifications:
secure_path
to include /usr/java/jre/bin/
.Defaults env_keep += “JAVA_HOME”
.The application can eventually be executed using the following:
/chp05/javaLED$ sudo sh ‐c 'java exploringBB.LEDExample On'
/chp05/javaLED$ sudo sh ‐c 'java exploringBB.LEDExample Off'
C++ was developed by Bjarne Stroustrup at Bell Labs (now AT&T Labs) during 1983–1985. It is based on the C language (named in 1972) that was developed at AT&T for UNIX systems in the early 1970s (1969–1973) by Dennis Ritchie. As well as adding an object‐oriented (OO) framework (originally called “C with Classes”), C++ also improves the C language by adding features such as better type checking. It quickly gained widespread usage, which was largely due to its similarity to the C programming language syntax, and the fact that it allowed existing C code to be used when possible. C++ is not a pure OO language but rather a hybrid, having the organizational structure of OO languages but retaining the efficiencies of C, such as typed variables and pointers.
Unlike Java, C++ is not “owned” by a single company. In 1998 the ISO (International Organization for Standardization) committee adopted a worldwide uniform language specification that aimed to remove inconsistencies between the various C++ compilers (Stroustrup, 1998).4 This standardization continues today with C++11 approved by the ISO in 2011 (gcc 4.7+ supports the flag ‐std=c++11
) and more new features appearing in compilers today with the approval of C++14 in August 2014.
Why am I covering C and C++ in more detail than other languages in this book?
Table 5-4 lists some advantages and disadvantages of using C/C++ on the BBB. The next section reviews some of the fundamentals of C and C++ programming, in order to ensure that you have the skills necessary for the remaining chapters in this book. It is not possible to cover every aspect of C and C++ programming in just one chapter of one book. The Further Reading section at the end of this chapter directs you to recommended texts.
Table 5-4 Advantages and Disadvantages of C/C++ on the BBB
ADVANTAGES | DISADVANTAGES |
You can build code directly on the BBB or you can cross‐compile code. The C/C++ languages are ISO standards, not owned by a single vendor. | Compiled code is not portable. Code compiled for your x86 desktop will not run on the BBB ARM processor. |
C++ has full support for procedural programming, OOP, and support for generics through the use of STL (Standard Template Library). | Many consider the languages to be complex to master. There is a tendency to need to know everything before you can do anything. |
It gives the best computational performance, especially if optimized; however, optimization can be difficult and can reduce the portability of your code. | The use of pointers and the low‐level control available makes code prone to memory leaks. With careful coding these can be avoided and can lead to efficiencies over dynamic memory management schemes. |
Can be used for user‐interface application development on the BBB using third‐party libraries. Libraries such as Qt and Boost provide extensive additional libraries for components, networking, etc. | By default, C and C++ do not support graphical user interfaces, network sockets, etc. Third‐party libraries are required. |
Offers low‐level access to glibc for integrating with the Linux system. Programs can be setuid to root. |
Not suitable for scripting (there is a C shell, csh, that does have syntax like C). Not ideal for web development either. |
The Linux kernel is written in C and having knowledge of C/C++ can help if you ever have to write device drivers or contribute to Linux kernel development. | C++ attempts to span from low‐level to high‐level programming tasks, but it can be difficult to write very scalable enterprise or web applications. |
The next section provides a revision of the core principles that have been applied to examples on the BBB. It is intended to serve as an overview and a set of reference examples that you can come back to again and again. It also focuses on topics that cause my students difficulties, pointing out common mistakes. Also, please remember that course notes for my OOP module are publicly available at ee402.eeng.dcu.ie
along with further support materials.
The following examples can be edited using the nano editor and compiled on the BBB directly using the gcc and g++ compilers, which are installed by default. The code is in the directory chp05/overview
.
The first example you should always write in any new language is “Hello World.” Listing 5‐6 and 5‐7 provide C and C++ code respectively, for the purpose of a direct comparison of the two languages.
The #include
call is a pre‐processor directive that effectively loads the contents of the stdio.h
file (/usr/include/stdio.h
) in the C case, and the iostream
header (/usr/include/c++/4.X/iostream
) file in the C++ case, and copies and pastes the code in at this exact point in your source code file. These header files contain the function prototypes, enabling the compiler to link to and understand the format of functions such as printf()
in stdio.h
and streams like cout
in iostream
. The actual implementation of these functions is in shared library dependencies. The angular brackets (< >) around the include filename means that it is a standard, rather than a user‐defined include
(which would use double quotes).
The main()
function is the starting point of your application code. There can only be one function called main()
in your application. The int
in front of main()
indicates that the program will return a number back to the shell prompt. As stated before, it is good to use 0
for successful completion, 2
for invalid usage, and any other set of numbers to indicate failure conditions. This value is returned to the shell prompt using the line return 0
in this case. The main()
function will return 0
by default. Remember that you can use echo $?
at the shell prompt to see the last value that was returned.
The parameters of the main()
function are int argc
and char *argv[]
. As you saw in the scripting examples, the shell can pass arguments to your application, providing the number of arguments (argc
) and an array of strings (*argv[]
). In C/C++ the first argument passed is argv[0]
and it contains the name and full path used to execute the application.
The C code line printf(“Hello World!
”);
allows you to write to the Linux shell, with the
representing a new line. The printf()
function provides you with additional formatting instructions for outputting numbers, strings, etc. Note that every statement is terminated by a semicolon.
The C++ code line std::cout << "Hello World!" << std::endl;
outputs a string just like the printf()
function. In this case cout
represents the output stream; and the function used is actually the <<
, which is called the output stream operator. The syntax is discussed later, but std::cout
means the output stream in the namespace std
. The endl
(end line) representation is the same as
. This may seem more verbose, but you will see why it is useful later in the discussion on C++ classes. These programs can be compiled and executed directly on the BBB by typing the following:
/chp05/overview$ gcc helloworld.c ‐o helloworldc
/chp05/overview$ ./helloworldc
Hello World!
/chp05/overview$ g++ helloworld.cpp ‐o helloworldcpp
/chp05/overview$ ./helloworldcpp
Hello World!
The sizes of the C and C++ executables are different to account for the different header files, output functions, and exact compilers that are used:
/chp05/overview$ ls ‐l helloworldc*
‐rwxr‐xr‐x 1 molloyd users 5053 Mar 14 12:23 helloworldc
‐rwxr‐xr‐x 1 molloyd users 6559 Mar 14 12:24 helloworldcpp
You just saw how to build a C or C++ application, but there are a few intermediate steps that are not obvious in the preceding example, as the intermediate stage outputs are not retained by default. Figure 5-1 illustrates the full build process from preprocessing right through to linking.
You can perform the steps in Figure 5-1 yourself. Here is an example of the actual steps that were performed using the Helloworld.cpp
code example. The steps can be performed explicitly as follows, so that you can view the output at each stage:
/chp05/overview$ ls
helloworld.cpp
/chp05/overview$ g++ ‐E helloworld.cpp > processed.cpp
/chp05/overview$ ls
helloworld.cpp processed.cpp
/chp05/overview$ g++ ‐S processed.cpp ‐o helloworld.s
/chp05/overview$ ls
helloworld.cpp helloworld.s processed.cpp
/chp05/overview$ g++ ‐c helloworld.s
/chp05/overview$ ls
helloworld.cpp helloworld.o helloworld.s processed.cpp
/chp05/overview$ g++ helloworld.o ‐o helloworld
/chp05/overview$ ls
helloworld helloworld.cpp helloworld.o helloworld.s processed.cpp
/chp05/overview$ ./helloworld
Hello World!
You can see the text format output after preprocessing by typing less processed.cpp
, where you will see the necessary header files pasted in at the top of your code. At the very bottom of the file you will find your code. This file is passed to the C/C++ compiler, which validates the code and generates platform‐independent assembler code (.s
). You can view this code by typing less helloworld.s
, as illustrated in Figure 5-1.
This .s
text file is then passed to the assembler, which converts the platform‐independent instructions into binary instructions for the BBB platform (the .o
file). You can see the assembly language code that was generated if you use the objdump
(object file dump) tool on the BBB by typing objdump ‐D helloworld.o
, as illustrated in Figure 5-1.
Object files contain generalized binary assembly code that does not yet provide enough information to be executed on the BBB. However, after linking the final executable code, helloworld
contains the target‐specific assembly language code that has been combined with the libraries, statically and dynamically as required—you can use the objdump
tool again on the executable, which results in the following output:
/chp05/overview$ objdump ‐D helloworld | less
helloworldcpp: file format elf32-littlearm
Disassembly of section .interp:
00008134 <.interp>:
8134: 62696c2f rsbvs r6, r9, #12032 ; 0x2f00
8138: 2d646c2f stclcs 12, cr6, [r4, #-188]! ; 0xffffff44
813c: 756e696c strbvc r6, [lr, #-2412]! ; 0x96c
...
The meaning of these instructions is another book in and of itself (see infocenter.arm.com
for reference documentation), but for example in this case the mnemonic rsbvs
refers to a type of reverse subtraction on the two of the ARM processor’s 16, 32‐bit registers (labeled r0–r15). Finally, just so you are aware, it is possible to disassemble executable code, in order that the source code can be reverse engineered. For example:
/chp05/overview$ objdump ‐S --disassemble helloworld > test.dump
/chp05/overview$ less test.dump
Is the HelloWorld
example the shortest program that can be written in C or C++? No, Listing 5‐8 is the shortest valid C and C++ program.
This is a fully functional C and C++ program that compiles with no errors and works perfectly, albeit with no output. Therefore, in building a C/C++ program, there is no need for libraries; there is no need to specify a return type for main()
, as it defaults to int
; the main()
function returns 0
by default in C++ and a random number in C (see echo $?
call below); and an empty function is a valid function. This program will compile as a C or C++ program as follows:
/chp05/overview$ gcc short.c ‐o shortc
/chp05/overview$ g++ short.c ‐o shortcpp
/chp05/overview$ ls ‐l short*
-rw-r--r-- 1 molloyd users 9 Mar 14 12:34 short.c
-rwxr-xr-x 1 molloyd users 4912 Mar 14 12:40 shortc
-rwxr-xr-x 1 molloyd users 5040 Mar 14 12:40 shortcpp
/chp05/overview$ ./shortc
/chp05/overview$ echo $?
97
/chp05/overview$ ./shortcpp
/chp05/overview$ echo $?
0
This is one of the greatest weaknesses of C and C++. There is an assumption that you know everything about the way the language works before you write anything. In fact, aspects of the preceding example might be used by a programmer to demonstrate how clever they are, but they are actually demonstrating poor practice in making their code unreadable by less “expert” programmers. For example, if you rewrite the C++ code in short.cpp
to include comments and explicit statements, to create short2.cpp
, and then compile both using the ‐O3
optimization flag, the output will be as follows:
/chp05/overview$ more short.cpp
main(){}
/chp05/overview$ more short2.cpp
// A really useless program, but a program nevertheless
int main(int argc, char **argv){
return 0;
}
/chp05/overview$ g++ ‐O3 short.cpp ‐o short1
/chp05/overview$ g++ ‐O3 short2.cpp ‐o short2
/chp05/overview$ ls ‐l short*
-rw-r--r-- 1 molloyd users 9 Mar 14 13:20 short.cpp
‐rwxr‐xr‐x 1 molloyd users 5030 Mar 14 13:28 short1
‐rwxr‐xr‐x 1 molloyd users 5031 Mar 14 13:28 short2
-rw-r--r-- 1 molloyd users 107 Mar 14 12:59 short2.cpp
Note that the difference in the executable size is only one byte! Adding the comment, the explicit return statement, the explicit return type, and explicit arguments has had no impact on the size of the final binary application. The binary executable is one byte longer because the source filename is one byte longer (i.e., short2.cpp
rather than short.cpp
). However, the benefit is that the actual functionality of the code is much more readily understood by a novice programmer.
You can build with the flag ‐static
to statically link the libraries, rather than the default form of linking dynamically with shared libraries. This means that the compiler and linker effectively place all the library routines required by your code directly within the program executable:
/chp05/overview$ g++ ‐O3 short.cpp ‐static ‐o shortst
/chp05/overview$ ls ‐l shortst
-rwxr-xr-x 1 molloyd users 455914 Mar 14 12:46 shortst
It is clear that the program executable size has grown significantly. One advantage of this form is that the program can be executed by ARM systems on which the C++ standard libraries are not installed.
With dynamic linking, it is useful to note that you can discover which shared library dependencies your compiled code is using, by calling ldd
:
/chp05/overview$ ldd short1
libstdc++.so.6 => /usr/lib/arm‐linux‐gnueabihf/libstdc++.so.6 (0xb...)
libm.so.6 => /lib/arm-linux-gnueabihf/libm.so.6 (0xb6ea4000)
libgcc_s.so.1 => /lib/arm-linux-gnueabihf/libgcc_s.so.1 (0xb6e80000)
libc.so.6 => /lib/arm-linux-gnueabihf/libc.so.6 (0xb6d9b000)
/lib/ld-linux-armhf.so.3 (0xb6fc9000)
You can see that the g++ compiler (and glibc) on the Debian image for the BBB has been patched to support the generation of hard floating‐point (HardFP) instructions by default. This allows for faster code execution with floating‐point numbers than if it used the soft floating‐point ABI (application binary interface) to emulate floating‐point support in software (SoftFP ABI).
A variable is a data item stored in a block of memory that has been reserved for it. The type of the variable defines the amount of memory reserved and how it should behave (see Figure 5-2). This figure describes the output of the code example sizeofvariables.c
in Listing 5‐9.
Listing 5‐9 details various variables available in C/C++. When you create a local variable c
below, it is allocated a box/block of memory on the stack (predetermined reserved fast memory) depending on its type. In this case, c
is an int
value; therefore, four bytes (32 bits) of memory is allocated to store the value. Assume that variables in C/C++ are initialized with random values; therefore, in this case c = 545;
replaces that initial random value by placing the number 545
in the box. It does not matter if you store the number 0
or 2,147,483,647
in this box: it will still occupy 32 bits of memory! Please note that there is no guarantee regarding the ordering of local variable memory—it was fortuitously linear in this particular example.
The function sizeof(c)
returns the size of the type of the variable in bytes. In this example, it will return 4
for the size of the int
type. The &c
call can be read as the “address of” c
. This provides the address of the first byte that stores the variable c
, in this case returning 0xbe8d6688
. The %.4f
on the first line means display the floating‐point number to four decimal places. Executing this program on the BBB gives the following:
/chp05/overview$ ./sizeofvariables
a val 3.1416 & size 8 bytes (@addr 0xbe8d6690).
b val 25.00 & size 4 bytes (@addr 0xbe8d668c).
c val 545 (oct 1041, hex 221) & size 4 bytes (@addr 0xbe8d6688).
d val 123 & size 4 bytes (@addr 0xbe8d6684).
e val A & size 1 bytes (@addr 0xbe8d6683).
f val 1 and size 1 bytes (@addr 0xbe8d6682).
The BBB has a 32‐bit microcontroller, so you are using four bytes to represent the int
type. The smallest unit of memory that you can allocate is one byte, so, yes, you are representing a Boolean value with one byte, which could actually store eight unique Boolean values. You can operate directly on variables using operators. The program operators.c
in Listing 5‐10 contains some points that often cause difficulty in C/C++:
This will give the following output:
/chp05/overview$ ./operators
The value of c=2 and a=2.
The value of d=2 and b=3.
The value of f=10.00 and e=9.
The value of g=65 and g=A.
On the line c=++a;
the value of a
is pre‐incremented before the equals assignment to c
on the left‐hand side. Therefore, a
was increased to 2
before assigning the value to c
, so this line is equivalent to two lines: a=a+1; c=a;
. However, on the line d=b++;
the value of b
is post‐incremented and is equivalent to two lines: d=b; b=b+1;
The value of d
is assigned the value of b
, which is 2
, before the value of b
is incremented to 3
.
On the line e=(int)f;
a C‐style cast is being used to convert a floating‐point number into an integer value. Effectively, when programmers use a cast they are notifying the compiler that they are aware that there will be a loss of precision in the conversion of a floating‐point number to an int
. The fractional part will be truncated, so 9.9999
will be converted to e=9
, as the.9999
is removed by the truncation. One other point to note is that the printf(“%.2f”,f)
will display the floating‐point variable to two decimal places, in contrast, rounding the value.
On the line g='A'
, g
is assigned the ASCII equivalent value of capital A, which is 65
. The printf(“%d %c”, g, g);
will display either the int
value of g
if %d
is used, or the ASCII character value of g
if %c
is used.
A const keyword can be used to prevent a variable from being changed. There is also a volatile keyword that is useful for notifying the compiler that a particular variable might be changed outside its control, and that the compiler should not apply any type of optimization to that value. This notification is useful on the BBB if the variable in question is shared with another process or physical input/output.
It is possible to define your own type in C/C++ using the typedef keyword. For example, if you did not want to include the header file stdbool.h
in the sizeofvariables.c
previous example, it would be possible to define it in this way instead:
typedef char bool;
#define true 1
#define false 0
Probably the most common and most misunderstood mistake in C/C++ programming is present in the following code segment:
if (x=y){
// perform a body statement Z
}
When will the body statement Z
be performed? The answer is whenever y
is not equal to 0
(the current value of x
is irrelevant!). The mistake is placing a single =
(assignment) instead of ==
(comparison) in the if
statement. The assignment operator returns the value on the RHS, which will be automatically converted to true
if y
is not equal to 0
. If y
is equal to zero, then a false
value will be returned. Java does not allow this error, as there is no implicit conversion between 0
and false
and 1
and true
.
A pointer is a special type of variable that stores the address of another variable in memory—we say that the pointer is “pointing at” that variable. Listing 5‐11 is a code example that demonstrates how you can create a pointer p
and make it point at the variable y
:
When this code is compiled and executed, it will give the following output:
/chp05/overview$ ./pointers
The variable has value 1000 and the address 0xbede26bc.
The pointer stores 0xbede26bc and points at value 1000.
The pointer has address 0xbede26b8 and size 4.
So, what is happening in this example? Figure 5-3 illustrates the memory locations and the steps involved. In Step 1, the variable y
is created and assigned the initial value of 1000
. A pointer p
is then created with the dereference type of int
. In essence, this means that the pointer p
is being established to point at int
values. In Step 2, the statement p = &y;
means “let p
equal the address of y
,” which sets the value of p
to be the 32‐bit address 0xbede26bc
. We now say that p
is pointing at y
. These two steps could have been combined using the call int *p = &y;
(i.e., create a pointer p
of dereference type int
and assign it to the address of y
).
Why does a pointer need a dereference type? For one example, if a pointer needs to move to the next element in an array, then it needs to know whether it should move by four bytes, eight bytes, etc. Also, in C++ you need to be able to know how to deal with the data at the pointer based on its type. Listing 5‐12 is an example of working with pointers and arrays.
This will give the output as follows:
/chp05/overview$ ./pointerarray
The value of x[0] is 101
The value of x[1] is 201
The value of x[2] is 301
The value of x[3] is 401
The value of x[4] is 501
In this example, the pointer p
is of dereference type int
and it is set to point at the first element in an array of five int
values using the statement int *p = &x[0];
. The line of code to focus on here is (*(p++))++;
(this is why pointers have a bad name!). Please note that the ()
are very important, as they set the order of operations. In this case the inner (p++)
makes the pointer point at the next element in the array; however, because it is a post‐increment, the increment happens after the end of this line. Therefore, this entire line could be written as :(*p)++; p++;
. The (*p)++
means increment the value stored at the address, changing 100
to 101
on the first iteration of the for
loop. Pointer notation can be complex, but remember that the dereference type is needed because in this case the p++
call needs to know to move four bytes forward in memory. If p
were of dereference type double (i.e., double *p;
), then the pointer would move by eight bytes when p++;
is called.
Part of the difficulty of using pointers in C/C++ is understanding the order of operations in C/C++, called the precedence of the operations. For example, if you write the statement
int x = 1 + 2 * 3;
what will the value of x
be? In this case it will be 7, because in C/C++ the multiplication operator has a higher level of precedence than the addition operator. The same issue occurs if you believe that you are incrementing the value at x
by the statement *x++
, as the ++
operator will be applied before the dereference operator, incrementing the pointer and uselessly dereferencing x
.
There are approximately 58 operators in C++, with 18 different major precedence levels. Even if you know the precedence table, you should still make it clear for other users what you intend in a statement by using round brackets (()
), which have the highest precedence level after the scope resolution (::
), increment (++
), and decrement (--
) operators. Therefore, you should always write the following:
int x = 1 + (2 * 3);
Finally, on the topic of C pointers, there is also a void* pointer that can be declared as void *p;
, which effectively states that the pointer p
does not have a dereference type and it will have to be assigned at a later stage (see /chp05/overview/void.c
) using the following syntax:
int a = 5;
void *p = &a;
printf("p points at address %p and value %d
", p, *((int *)p));
When executed, this code will give an output like the following:
The pointer p points at address 0xbea546c8 and value 5
Therefore, it is possible to cast a pointer from one deference type to another and the void pointer can potentially be used to store a pointer of any dereference type. In Chapter 6 void pointers are used to develop an enhanced GPIO interface.
The C language has no built‐in string type but rather uses an array of the character type, terminated by the null character (