Command Line and Function Arguments: Useful C/C++ Features Part 2

Author: Greg Gutmann
Affiliation: Tokyo Institute of Technology

Introduction

This is the second post in the series showcasing features of the C and C++ languages that I will be using in my CUDA posts. This post will look at the various ways to pass variables into functions as well as reading arguments passed in when running from a command line interface.
Click here to see post 1.

Clarification on terms: A parameter is a variable in the declaration of a function. The actual value of this variable which is passed to Function is called an argument. [1]

Passing Arguments to Functions

The following sample code shows various methods of passing single variables into functions (in contrast to passing pointers to arrays of variables).

Caution: the SyntaxHighlighter plugin used for the code block has an error at the time of posting. If you see “&” replace it with “&”.

#include <stdio.h>      // printf 

int addOneA(int input){
	return input + 1;// Pass-by-value: Locally incremented and result is returned
}

void addOneB(int input){	
	input += 1;		// Locally incremented, value passed in will be unchanged 
					//		after function call.
					// Can be used for functions that require inputs but 
					//		do not need to return or modify the value passed in.
}

void addOneC(int * input){
	*input += 1;	// Pass-by-pointer: can modify the value that the input pointer points to
}

void addOneD(int & input){
	input += 1;		// Pass-by-reference: can modify the input by using its reference passed in (C++)
}

void setABC(int * a, int * b, int * c){
	*a = 1;			// Pointers are useful if many values need to be modified and "returned"
	*b = 2;
	*c = 3;
}

int main() {

	int dataValue = 0;
	printf("dataValue initial value: %d\n", dataValue);
	
	dataValue = addOneA(dataValue);
	printf("dataValue after addOneA: %d\n", dataValue);
	
	addOneB(dataValue);
	printf("dataValue after addOneB: %d (unchanged)\n", dataValue);
	
	addOneC(&dataValue);
	printf("dataValue after addOneC: %d\n", dataValue);
	
	addOneD(dataValue);
	printf("dataValue after addOneD: %d\n", dataValue);

	int a, b, c;	// Careful with uninitialized data, not all types set to 0 always
	printf("a: %d, b: %d, c: %d\n", a, b, c);
	
	setABC(&a, &b, &c);
	printf("a: %d, b: %d, c: %d\n", a, b, c);
	
	return 0;
}
Compile Command:
gcc argSample0.cpp -o argSample0
Sample's Output:
dataValue initial value: 0
dataValue after addOneA: 1
dataValue after addOneB: 1 (unchanged)
dataValue after addOneC: 2
dataValue after addOneD: 3
a: 0, b: 0, c: 0
a: 1, b: 2, c: 3

An additional resource for passing arguments: https://www.ibm.com/support/knowledgecenter/en/SSLTBW_2.3.0/com.ibm.zos.v2r3.cbclx01/cfpv.htm

Command Line Arguments

The next sample shows a simple case which reads command line arguments and prints them as strings.

One of the notable changes in this sample is that the main function now has “int argc, char *argv[]”. These are standardized names and parameters, they will not work if changes to other names. argc is the count of arguments from 0, including executable. argv is an array of pointers to char arrays (C-string).

#include <stdio.h>      // printf 

int main(int argc, char *argv[]) {
	
	printf("argc:     %d\n", argc);
	printf("argv[0]:  %s\n", argv[0]);	// The [0] spot is the executable

	if (argc == 1) {
		printf("No arguments were passed, will run with default values.\n");
	}
	else {
		printf("Arguments received:\n");
		for (int i = 1; i < argc; ++i) {
			printf("  %d. %s\n", i, argv[i]);
		}
	}
	
	/* Imaginary Code*/
	
	printf("Finished running, now closing.\n");

	return 0;
}
Compile Command:
gcc argSample1.cpp -o argSample1

Command and Possible Output:
./argSample1
argc: 1
argv[0]: ./argSample1
No arguments were passed, will run with default values.
Finished running, now closing.

Command and Possible Output:
./argSample1 Hello my name is…
argc: 5
argv[0]: ./argSample1
Arguments received:
Hello
my
name
is…
Finished running, now closing.

Expanding Functionality

The next sample demonstrates two ways to make passing arguments at the command line more flexible. Note that there are many ways to manage the strings passed in, so these are not the only approaches.

This sample also includes a switch statement (link to an additional explanation at the end) and shows how pointers can be offset by adding integers to them. Offsetting a pointer with an integer offsets the pointer by the size of the data type times the integer added.

#include <stdio.h>      // printf, sscanf
#include <stdlib.h>     // atoi atof
#include <string.h>	// strlen, memset, memcpy

#define STR_BUFF_LEN 64

// Note a char array is often called a C-string

// Allows for various numbers of inputs in any order
// Contains small amounts of error checking but is not robust
// Ex. once made into a case statement it assumes correct input
void takeInputsA(int argc, char *argv[], char * inString, int * loopCount, float * multiplier) {
	bool formatError = false;

	if (argc == 1) {
		printf("No arguments were passed.\n");
	}
	else {
		for (int i = 1; i < argc; ++i) {
			if (argv[i][0] == '-') {
				switch (argv[i][1]) {
				case 's':
				{
					char * strStart = argv[i] + 2; // Another way to write "argv[i][2]"
					int length = strlen(strStart);
					
					// Set memory to 0 (empty) in case the new string is smaller than the old string
					memset(inString, 0, STR_BUFF_LEN * sizeof(char)); 
					// Copy the input string into the char buffer
					memcpy(inString, strStart, length * sizeof(char)); 
					break;
				}
				case 'l':
				{
					// Interprets contents of char array as an int and returns its result
					*loopCount = atoi(argv[i] + 2);
					break;
				}
				case 'm':
				{
					// Interprets contents of char array as a float and returns its result
					*multiplier = atof(argv[i] + 2); 
					break;
				}
				default:
					formatError = true;
				}
			}
			else {
				formatError = true;
			}
		}
		if (formatError) {
			printf("There were formatting errors in the input\n");
		}
	}
}

// Simple approach which assumes the input is in the correct format,
//  order and includes all parameters
void takeInputsB(int argc, char *argv[], char * inString, int * loopCount, float * multiplier) {
	int readInCount = 0;
	sscanf(argv[1], "%s", inString);
	sscanf(argv[2], "%d", loopCount);
	sscanf(argv[3], "%f", multiplier);
	// sscanf can be used to extract many values from a single char array, see link
}

int main(int argc, char *argv[]) {

	// Code parameters with default values
	char inString[STR_BUFF_LEN] = { "Hello" };
	int loopCount = 10;
	float multiplier= 2.1f;
	
#ifdef INPUTA
	takeInputsA(argc, argv, inString, &loopCount, &multiplier);
#endif // INPUTA

#ifdef INPUTB
	takeInputsB(argc, argv, inString, &loopCount, &multiplier);
#endif // INPUTB

	printf("String Input: %s\n", inString);
	printf("loopCount:    %d\n", loopCount);
	printf("multiplier:   %4.2f\n", multiplier);

	// *Imaginary code that uses the inputs*

	return 0;
}

takeInputsA uses flags to tell the code what arguments are being given. The flags, in this case, are a single “-” and a character following it to tell what type of argument is being given. This allows the user to only pass in the arguments which they wish to change and in any order. For an explanation on the use of #ifdef see post 1 of this series.

 Compile Command: 
gcc argSample2.cpp -o argSample2A -DINPUTA

Command and Possible Output:
./argSample2A
No arguments were passed.
String Input: Hello
loopCount: 10
multiplier: 2.10

Command and Possible Output:
./argSample2A -sText -l44 -m33.3
String Input: Text
loopCount: 44
multiplier: 33.30

Command and Possible Output:
./argSample2A -m33.3 -sText
String Input: Text
loopCount: 10
multiplier: 33.30

takeInputsB uses the built-in function sscanf. sscanf reads formatted input from a string, the format is specified in code and therefore fixed. The first output below is a result of passing in the expected arguments, it works as expected. The second output is a result of passing in unexpected arguments (no arguments), it returns with errors.

Compile Command:
gcc argSample2.cpp -o argSample2B -DINPUTB

Command and Possible Output:
./argSample2B Text 44 33.3
String Input: Text
loopCount: 44
multiplier: 33.30

Command and Possible Output:
./argSample2B
Exception: STATUS_ACCESS_VIOLATION
… stack trace was here …
Segmentation fault (core dumped)

Side note: Quotation marks, single or double, can be used to pass in a string that contains spaces.

Additional pages on functionality not explained in depth
Switch statement: https://www.tutorialspoint.com/cplusplus/cpp_switch_statement.htm
sscanf: https://www.tutorialspoint.com/c_standard_library/c_function_sscanf.htm

Conclusion

This post has only scratched the surface on data input into functions and the programs themselves. There are countless ways and levels of data input and output. But for now the material covered here will be sufficient for understanding my posts looking at CUDA programming.

Contact me if you would like to use the contents of this post. Thanks 🙂
Copyright © 2019 by Gregory Gutmann

Leave a Reply

Close Menu