C Struct Member Assignment

Lesser known C features

There are many C features which are lesser known even to experienced programmers.

Array of a variable size

Do not be confused with dynamic arrays. C99 supports arrays of variable length where the size is calculated at run-time while processing the array definition. This is a back-port from C++ and far better than the function. Have a look at the example below.

int fo( int i) { return i+1; } void f( int len, char s[ len][ fo( len+1)*2]) { for (int i=1; i<10; i++) { char s1[ i+len]; ... } }

It is not possible to skip the definition using break in switch statement.

Flexible array as a member of a structure

Arrays with unspecified size are used in structures to help addressing data past the structure end.

struct s { int i; unsigned u; char end[]; };

There are several restrictions. The flexible array member must be the last element of the structure and the structure itself must be neither used in another structure definition nor as a member of an array.

Structure field addressing in definitions

In old C the fields of a structure have to be in fixed order during initialization. It is a well-known GNU extension that specifies structure fields with the label-like syntax. C99 has a new approach using the . operator. Although it looks strange, it has a hidden meaning (see below).

struct fops { int open, read, write, close; }; { struct fops f1 = { open: 0, close: 1, read: 2}; struct fops f2 = { .open=0, .close=1, .read=2} }; }

Array initialization

It is not very well known that an array initiator may jump in index like in the enumeration definition.

int a[ 7] = { [5]=1, [2]=3, 2}; int a[ 7] = { 0, 0, 3, 2, 0, 1, 0}; struct { int x,y; } ar[ 4] = { [1].x=23, [3].y=34, [1].y=-1, [1].x=12}; struct { int x,y; } ar[ 4] = { { 0, 0}, { 12, -1}, { 0, 0}, { 0, 34}}; char forbidden[ 256] = { ['a']=1, ['e']=1, ['i']=1, ['o']=1, ['u']=1};

Note that already initialized fields may be overwritten.

Compound literals

Compounds literals bring a new method of assigning to structures and passing structures as parameters.

struct point { int x, y; }; void foo( struct point p1, struct point p2); { struct point p1 = { 2, 4}; p1 = (struct point){ 1, 3}; foo( (struct point){ 10, 11}, (struct point){ 1, 2}); char **sx = (char *[]){ "Adam", "Eva", "Simon"}; }

Inline functions

Yes, C has inline functions. Like in C++ prepend the function header with the keyword ( in headers). Note that certain constructions may disallow the compiler to inline the function. Especially constructions implemented on stack like variadic parameters, alloca or variable sized arrays.

Volatile type qualifier

Have a look at the example below - there are two functions each executed in a separate thread with the same pointer as a parameter. Do you think that the function must terminate?

void add( int *i) { while (1) { *i = 0; for (int a=0; a<10; a++) (*i)++; } } void check( int *i) { while (*i == 5) ; }

No, it doesn't have to. The incrementation may by optimized. Below is a disassembled code produced by GCC -O1:

<add+00> push ebp <add+01> mov ebp,esp <add+03> mov ecx,DWORD PTR [ebp+8] <add+06> mov DWORD PTR [ecx],0x0 <add+12> mov eax,0x0 <add+17> mov edx,0x0 <add+22> inc edx <add+23> inc eax <add+24> cmp eax,0x9 <add+27> jle <add+22> <add+29> mov DWORD PTR [ecx],edx <add+31> jmp <add+6>

As we can see, has been replaced by the edx register, which is written only at the beginning and at the end of the cycle. The value of switches between 0 and 10, and never becomes 5.

Marking the pointer as will solve the problem. The semantics is that a volatile object may change its value outside the scope of local execution and thus every read and write access has to be processed immediately without any optimizations. Focus on this problem when you observe different behavior across different optimization levels. It is not guaranteed that a read or write access even on volatile objects are atomic.

Restricted pointers

The freedom of pointers in C leads sometimes to slower code. The programmer can enable several optimizations by guarantying that objects referred by pointers do not overlap.

void copy1( char *s1, char *s2, int n) { while (n--) *s1++ = *s2++; } void copy2( charrestrict *s1, charrestrict *s2, int n) { while (n--) *s1++ = *s2++; }

Both functions copy one block of chars into another. In the compiler may use word addressing instructions to speedup the execution. But when blocks overlap the behavior is undefined.

Macros with variable number of parameters

The syntax is similar to functions. Parameters in ... are then addressed as (variable argument).

#define myfunc( A, B, ...) do_something( 0, B, A, __VA_ARGS__);

Some predefined identifiers

  • expands to a string name of a current source file
  • expands to a string name of a current function
  • is expanded to the line number. This can be overwritten with the preprocessor directive
  • date of compilation
  • time of compilation
  • is expanded to long int which represents the ISO norm (199901L as an example)

Trigraphs

This may looks like a joke, but it is not. All occurrences of sequences ??<, ??>, ??(, ??), ??=, ??/, ??!, ??', ??- in a source file are converted to one of characters { } [ ] # / | ^ ~. So don't be surprised...

As an aside, tokens <: :> <% %> %: behave as [ ] { } # and ## (but the conversion is not performed in strings).

Types with defined size

Have a look at the header file . There are typedefined types like (where N is in { 8, 16, 32, 64})

  • - signed integers with exactly specified width
  • - signed integers with a width of at least N
  • - fastest signed integers with a width of at least N

Unsigned variants have prefix "u".

Be careful when using functions. Since you do not know which C type are behind these typedefs, you have to use predefined constants from . Constants starts with followed by type character (one of ), modificator or or nothing, and number of bits. For example, 32 bit fast integer would take the form .

Boolean type

C still does not have a boolean type, but reserves an integer type big enough to store 0 and 1. The header file only typedefines as a and defines constants as 0 and as 1.

This is not C++ !

Complex numbers

C has three complex types: {, , } . In the header file is typedefined to . Complex types may be not implemented in freestanding (without OS) implementations.


CS202 Computer Science II

Structures in C++

C/C++ allow you to define variables made up of other datatypes. Such structures are equivalent to records in pascal and are called a in ANSI C/C++.

struct

A consists of three parts: the keyword, an optional identifier called a tag, and the member data fields of the listed inside parentheses. The tag name is optional and can be left out.

The general syntax for a with tag is:

struct tagname { datatype1 name1; datatype2 name2; datatype3 name3; ... } variable_name; and without a tag: struct { datatype1 name1; datatype2 name2; datatype3 name3; ... } variable_name; Note that always ends with a semicolon (;). Here are a couple of examples: struct Person { struct { char name[20]; char name[20]; int age; int age; float salary; float salary; } tom; } tom; The above two s each have three data fields: an array of , an , and a .

is a datatype and can be used in exactly the same way as we use , , or other C++ datatypes. is not a variable name and to use a you must first define variables of that type. This can be done in two ways.

  1. When you originally define a you can define a variable or variables. In the definition above, is the name of a variable of type . We could declare more than one variable this way. All variable names must be placed between the closing right brace and the semicolon and must be separated by commas. struct Person { char name[20]; int age; float salary; } tom, dick, harry; declares three variables, and of type , each of which contains three data fields.
  2. If you included a tag name when you originally defined the , you can later declare other variables of that type: Person obj1, obj2;

    If you had not used a tag name when you originally defined the struct then you will have only one opportunity to declare variables of that structural type.

    struct { char name[20]; int age; float salary; } obj3, obj4, obj5; You would not be able to declare any more variables of that particular type at a later time.

Initializing Structs

A variable can be initialized, like any other data variables in C++, at the time it is declared. We could use:

struct Person { char name[20]; int age; float salary; } tom = {"Tom", 25, 40000.50} ; or Person tom = {"Tom", 25, 40000.50}; You CANNOT assign assign values inside the declaration itself. You cannot, for example use struct Person { char name[20] = {"Tom"}; /* WRONG */ .... } tom;

Accessing data fields in structs

Although you can only initialize in the aggregrate, you can later assign values to any data fields using the dot (.) notation. To access any data field, place the name of the variable, then a dot (.), and then the name of the data field.

Person obj1; obj1.salary = 20677.34; each data field can be individually used in any C++ expression as part of any legal C/C++ statement.
  1. in an assignment
  2. or

If two variables are of the same type, that is, they both were declared to be of the same type (same tag name) then we can copy one variable to another with a simple assignment statement. For example:

Person obj1; Person obj2 = {"Eric", 20, 10000.2}; obj1 = obj2; If both and were not declared to be of type we could not have copied them as simply. For example, we could NOT do this: struct Person1 { char name[20]; int age; float salary; } obj1 = {"Tom", 25, 40000.50} ; struct Person2 { char name[20]; int age; float salary; } obj2; obj2 = obj1; /* WRONG */ even though and have exactly the same internal structure.

are quite useful and versatile. They can be treated like C++'s predefined data types and can be passed as an argument to a function or returned from a function. Unlike arrays, instances are passed by value. A 's internal fields, its member fields, can individually also be treated just like any variable to be passed as arguments, returned, assigned, or used in expressions. Just remember to use the dot notation to access member fields of a .

Embedded structs

A can be embedded in another . More precisely, a can have a previously declared as a member. This comes in useful when we deal with data structures like linked lists and trees. Here's an example:

typedef char String9[10]; typedef char String15[16]; enum GenderType {male, female, unknown}; enum ClassType { freshman, sophomore,junior, senior, graduate, special }; struct NameType { String15 first; String15 last; char middleInitial; }; struct StudentType { NameType name; ClassType classification; String9 ssn; GenderType gender; float gpa; }; StudentType student; To access a student's middle initial, you have to access a that is itself a member of another . For example: student.name.middleInitial = "J"; Note that we need to use 2 dots to access an embedded structure. One dot to access the field and another to access a field within . In general, we will need to add another dot each time we move to a deeper level of embedding.

One dimensional arrays of structs

An array of is declared just like arrays of other data types in C. I general: tag arrayname[size] or for example: Person business[50]; declares an array of 50 items each of which is of type . It is also common to declare an array when you define a :

struct Person { char name[20]; int ID; char phone[10]; } business[50] To reference the array of structs you use a subscript (within []) to access the correct element of the array, then dot notation to access the correct member field. In general: array_name[subscript].struct_field = value or specifically: business[5].ID = 783; We don't usually initialize arrays of structs using the brace syntax, rather, we would probably read in the values from a file.

0 thoughts on “C Struct Member Assignment”

    -->

Leave a Comment

Your email address will not be published. Required fields are marked *