Students in the junior- and senior-level CS classes are expected to know some background information about C, Unix, and software-development tools. This page details some of this background and suggests some exercises.
extern int theVariableIf you have many files sharing the variable, you should declare it extern in a header file foo.h (described below) and use #include foo.h in files B.c, C.c, D.c, .... You must declare the variable in exactly one file without the extern modifier or it never gets allocated at all.
char *myString = "This is a sample string";
myString = ""; // empty string, length 0, containing null
myString = NULL; // invalid stringYou might use such a value to indicate the end of an array of strings:
argv[0] = "progName"; argv[1] = "firstParam"; argv[2] = "secondParam"; argv[3] = NULL; // terminator
char *myString; myString = (char *) malloc(strlen(someString) + 1); // allocate space strcpy(myString, someString); // copy someString to myString
free((void *) myString);To keep your code clean and readable, you should call free() in the same procedure in which you call malloc(); you can call other procedures between those two points to manipulate your string.
string myString // This declaration only works in C++ ... someCall(myString.c_str())Unfortunately, c_str() returns an immutable string. If you need a mutable string, you can either copy the data using strcpy() (as above) or you can cast the type:
someCall(const_cast<char *>(myString.c_str()))Casting is not as safe as copying, because someCall() might actually modify the string, which would confuse any part of the program that assumes that myString is constant, which is the usual behavior of C++ strings.
char *fileName = "/tmp/foo" #define BUFSIZE 4096 char buf[BUFSIZE]; // buffer containing at most BUFSIZE bytes ... int outFile; // file descriptor, a small integer int bytesToWrite; // number of bytes still to be written char *outPtr = buf; ... if ((outFile = creat(fileName, 0660)) < 0) { // failure // see file permissions to understand 0660 perror(fileName); // print cause exit(1); // and exit } bytesToWrite = 123; // initialization; 123 is just an example while ((bytesWritten = write(outFile, outPtr, bytesToWrite)) < bytesToWrite) { // not all bytes have been written yet if (bytesWritten < 0) { // failure perror("write"); exit(1); } outPtr += bytesWritten; bytesToWrite -= bytesWritten; }
#define BUFSIZE 1024 char buf[BUFSIZE];If you just declare the buffer with no size:
char buf[];then it has unknown size and C does not allocate any space. That's acceptable if buf is a formal parameter (that is, it appears in a procedure header); the actual parameter (provided by the caller) has a size. But it is not acceptable if buf is a variable. If you don't know the size of the buffer at compile time, you should use code like this:
char *buf = (char *) malloc(bufferSize);where bufferSize is the runtime result of some computation.
typedef ... myType; myType *myVariable = (myType *) malloc(sizeof(myType)); // you can now access *myVariable. ... free((void *) myVariable);Again, it is good programming practice to invoke free() in the same routine in which you call malloc().
myType *myArray = (myType *) malloc(arrayLength * sizeof(myType)); // myArray[0] .. myArray[arrayLength - 1] are now allocated. ... free((void *) myArray);
myType **myArray = (myType **) malloc(numRows * sizeof(myType *)); int rowIndex; for (rowIndex = 0; rowIndex < numRows; rowIndex += 1) { myArray[rowIndex] = (myType *) malloc(numColumns * sizeof(myType)); } // myArray[0][0] .. myArray[0][numColumns-1] .. myArray[numRows-1][numColumns-1] // are now allocated. You might want to initialize them. ... for (rowIndex = 0; rowIndex < numRows; rowIndex += 1) { free((void *) myArray[rowIndex]); } free((void *) myArray);
write(outFile, &myInteger, sizeof(myInteger))
int IPAddress; // stored as an integer, understood as 4 bytes typedef struct { char byte1, byte2, byte3, byte4; } IPDetails_t; IPDetails_t *details = (IPDetails_t *) (&IPAddress); printf("byte 1 is %o, byte 2 is %o, byte 3 is %o, byte 4 is %o\n", details->byte1, details->byte2, details->byte3, details->byte4);
struct foo { char a; // uses 1 byte // C inserts a 3-byte pad here so b can start on a 4-byte boundary int b; // uses 4 bytes unsigned short c; // uses 2 bytes unsigned char d[2]; // uses 2 bytes };Therefore, sizeof(struct foo) returns 12. This predictability (for a given architecture) is why some call C a "portable assembler language". You need to predict struct layout when generating data that must follow a specific format, such as a header on a network packet.
int someInteger; int *intPtr = &someInteger; // declares a pointer-valued variable and assigns an appropriate pointer value someCall(intPtr); // passes a pointer as an actual parameter someCall(&someInteger); // has the same effect as above
#define ARRAY_LENGTH 100 int intArray[ARRAY_LENGTH]; int *intArrayPtr; ... int sum = 0; for (intArrayPtr = intArray; intArrayPtr < intArray+ARRAY_LENGTH; intArrayPtr += 1) { sum += *intArrayPtr; }
#define ARRAY_LENGTH 100 typedef struct {int foo, bar;} pair_t; // pair_t is a new type pair_t structArray[ARRAY_LENGTH]; // structArray is an array of ARRAY_LENGTH pair_t elements pair_t *structArrayPtr; // structArrayPtr points to a pair_t element ... int sum = 0; for (structArrayPtr = structArray; structArrayPtr < structArray+ARRAY_LENGTH; structArrayPtr += 1) { sum += structArrayPtr->foo + structArrayPtr->bar; }
printf("I think that the number %d is %s\n", 13, "lucky");
int main(int argc; char *argv[]);Here, argc is the number of parameters, and argv is an array of strings, that is, an array of pointers to null-terminated character arrays.
int main(int argc; char *argv[]); printf("I have %d parameters; my name is %s, and my first parameter is %s\n", argc, argv[0], argv[1]);
myInt -= 3; // equivalent to myInt = myInt - 3 myInt *= 42; // equivalent to myInt = myInt * 42 myInt += 1; // equivalent to and maybe preferable to myInt++
myInt = myInt | 0444; // bitwise OR; 0444 is in octal myInt &= 0444; // bitwise AND with an assignment shorthand myInt = something ^ whatever; // bitwise XOR
if (a < 7) a = someValue else a = someOtherValue;you can write
a = a < 7 ? someValue : someOtherValue;
if ((s = socket(...)) == -1)not
if (s = socket(...) == -1)The second version is both harder to read and, in this case, incorrect, because the equality operator == has higher precedence than the assignment operator =.
gcc *.o -o myProgramThis command asks the compiler to link all the object files with the C library (which is implicitly included) and place the result in file myProgram, which becomes executable.
gcc *.o -lxml2 -o myProgramThe compiler knows how to search various standard directories for the current version of libxml2.
ls | wc
ls > lsOutFile wc < lsOutFile sort -u < largeFile > sortedFile
int fd; char *filename = "myfile"; if ((fd = open(filename, O_RDONLY)) < 0) { perror(filename); // might print "myfile: No such file or directory" }
print (myInt + 59) & 0444;
SOURCES = driver.c input.c output.c OBJECTS = driver.o input.o output.o HEADERS = common.h CFLAGS = -g -Wall program: $(OBJECTS) $(CC) $(CFLAGS) $(OBJECTS) -o program $(OBJECTS): $(HEADERS) testRun: program program < testDataThis makefile uses a built-in definition of CC and a built-in rule to convert C source files like driver.c into their object file. If you modify just input.c, then make testRun will cause the compiler to rebuild input.o, then cause the compiler to relink the objects, creating program, and then run program with standard input redirected from the file testData.
grep "struct timeval {" /usr/include/*/*.h