I am maintaining a large block of C code and attempting to improve it and I have run across a function which uses a syntax unfamiliar to me:
char (*(lgLinkStrs)())[CONFIG_MAX_STRING] {
...
}
I know that it returns a 2-dim array of chars (basically an array of strings) which was defined as a global variable elsewhere and that this isn't a best practice. I'm familiar with the common methods of passing in and returning arrays as pointers.
I don't understand this syntax and I can't even figure out what to google. And I can't improve the code until I understand it.
Could someone explain to me what this syntax means or at least where I can look it up?
Start with the leftmost identifier and work your way out, remembering that postfix [] and () have higher precedence than unary *, so
T *a[N]; // a is an array of pointers to T
T (*a)[N]; // a is a pointer to an array of T
T *f(); // f is a function returning pointer to T
T (*f)(); // f is a pointer to a function returning T
Thus:
lgLinkStrs -- lgLinkStrs
(lgLinkStrs) -- surrounded by superfluous parentheses
(lgLinkStrs)() -- is a function taking no arguments
*(lgLinkStrs)() -- returning a pointer to
(*(lgLinkStrs)())[CONFIG_MAX_STRING] -- an array of
char (*(lgLinkStrs)())[CONFIG_MAX_STRING] -- char
Another approach is to work from the outside in - you understand char, and you understand [CONFIG_MAX_STRING], collapse everything else to something simple:
char x[CONFIG_MAX_STRING];
Thus, x is a CONFIG_MAX_STRING-size array of char. Now expand x one step:
char x [CONFIG_MAX_STRING]
+-++
| |
V V
char (*u)[CONFIG_MAX_STRING]
Thus, u is a pointer to an array of char. We used *u instead of u() since () has higher precedence, and thus binds more tightly to the expression than *. Now expand u:
char (* u )[CONFIG_MAX_STRING]
+----+-----+
| |
V V
char (*lgLinkStrs())[CONFIG_MAX_STRING]
Thus, lgLinkStrs is a function returning a pointer to an array of char. Obviously, the extra parentheses surrounding lgLinkStrs in the original declaration are superfluous.
Edit
Some commenters have mentioned the "spiral rule", which isn't a rule per se, but falls out of the precedence rules I mentioned above:
+---------------+
| +--------+ |
| | +----+ | |
| | | | | |
char ( * f () )[N];
| | | | | |
| | +--+ | |
| +------+ |
+-----------+
lgLinkStrs is a function returning a pointer to an array of CONFIG_MAX_STRING char and you are defining the function here. The curly braces denote the body of the function.
Also follow the spiral rule here - you will easily get to the declaration.
Related
I was learning C language where I saw that pointers are variables that store the address of other variables. So I ran this code:
int x = 10;
int *p;
p = &x;
printf("%i\n", p);
Result: 6422292
Then I tried to do the same thing without using pointers, just using a variable to store the address:
int z = 10;
int v;
v = &z;
printf("%i", v);
Result: 6422282
Since we can use variables to store other variables' address, why do we use pointers at all?
Pointers are not integers. They may have integral representation, but they do not behave like integers and should not be treated like integers. Note that on platforms like x86_64 an int is not wide enough to store a pointer value.
Pointers are a distinct class of datatypes for storing the location of an object or function - they are an abstraction of a memory address, with additional type information. Remember, a data type isn't just about what values you can store, but also about what operations you can perform on those values. Pointer operations are distinct from integer operations. The + and - operators mean very different things for integer and pointer types. The unary * operator is not defined for integer types. The arithmetic * and / operators are not defined for pointer types.
And so on.
Pointers to different types are themselves different types and are not interchangeable. Pointer arithmetic (the basis of array subscripting) is based on the pointed-to type. That is, if cp is a char * pointing to a char object, then cp + 1 yields the location of the next char object immediately following. If ip is an int * pointing to an int object, then ip + 1 yields the location of the next int object immediately following:
+---+
c: | | <--- cp
+---+
| | <--- cp + 1
+---+
...
+---+
i: | | <--- ip
+---+
| |
+---+
| |
+---+
| |
+---+
| | <-- ip + 1
+---+
| |
+---+
| |
+---+
| |
+---+
...
This is what I mean about pointers not behaving like integers. They have their own distinct semantics.
C expects the operand of the unary * operator to have pointer type. If you try to deference an integer, even if that integer object stores a valid address value, the compiler will yell at you.
In case of integer, it looks like good, because address is itself integer, but try to do this with other data type like string , array and any struct . You will get the idea why we need pointer in C.
Going through some C interview questions, I've found a question stating "How to find the size of an array in C without using the sizeof operator?", with the following solution. It works, but I cannot understand why.
#include <stdio.h>
int main() {
int a[] = {100, 200, 300, 400, 500};
int size = 0;
size = *(&a + 1) - a;
printf("%d\n", size);
return 0;
}
As expected, it returns 5.
edit: people pointed out this answer, but the syntax does differ a bit, i.e. the indexing method
size = (&arr)[1] - arr;
so I believe both questions are valid and have a slightly different approach to the problem. Thank you all for the immense help and thorough explanation!
When you add 1 to a pointer, the result is the location of the next object in a sequence of objects of the pointed-to type (i.e., an array). If p points to an int object, then p + 1 will point to the next int in a sequence. If p points to a 5-element array of int (in this case, the expression &a), then p + 1 will point to the next 5-element array of int in a sequence.
Subtracting two pointers (provided they both point into the same array object, or one is pointing one past the last element of the array) yields the number of objects (array elements) between those two pointers.
The expression &a yields the address of a, and has the type int (*)[5] (pointer to 5-element array of int). The expression &a + 1 yields the address of the next 5-element array of int following a, and also has the type int (*)[5]. The expression *(&a + 1) dereferences the result of &a + 1, such that it yields the address of the first int following the last element of a, and has type int [5], which in this context "decays" to an expression of type int *.
Similarly, the expression a "decays" to a pointer to the first element of the array and has type int *.
A picture may help:
int [5] int (*)[5] int int *
+---+ +---+
| | <- &a | | <- a
| - | +---+
| | | | <- a + 1
| - | +---+
| | | |
| - | +---+
| | | |
| - | +---+
| | | |
+---+ +---+
| | <- &a + 1 | | <- *(&a + 1)
| - | +---+
| | | |
| - | +---+
| | | |
| - | +---+
| | | |
| - | +---+
| | | |
+---+ +---+
This is two views of the same storage - on the left, we're viewing it as a sequence of 5-element arrays of int, while on the right, we're viewing it as a sequence of int. I also show the various expressions and their types.
Be aware, the expression *(&a + 1) results in undefined behavior:
...
If the result points one past the last element of the array object, it
shall not be used as the operand of a unary * operator that is evaluated.
C 2011 Online Draft, 6.5.6/9
This line is of most importance:
size = *(&a + 1) - a;
As you can see, it first takes the address of a and adds one to it. Then, it dereferences that pointer and subtracts the original value of a from it.
Pointer arithmetic in C causes this to return the number of elements in the array, or 5. Adding one and &a is a pointer to the next array of 5 ints after a. After that, this code dereferences the resulting pointer and subtracts a (an array type that has decayed to a pointer) from that, giving the number of elements in the array.
Details on how pointer arithmetic works:
Say you have a pointer xyz that points to an int type and contains the value (int *)160. When you subtract any number from xyz, C specifies that the actual amount subtracted from xyz is that number times the size of the type that it points to. For example, if you subtracted 5 from xyz, the value of xyz resulting would be xyz - (sizeof(*xyz) * 5) if pointer arithmetic didn't apply.
As a is an array of 5 int types, the resulting value will be 5. However, this will not work with a pointer, only with an array. If you try this with a pointer, the result will always be 1.
Here's a little example that shows the addresses and how this is undefined. The the left-hand side shows the addresses:
a + 0 | [a[0]] | &a points to this
a + 1 | [a[1]]
a + 2 | [a[2]]
a + 3 | [a[3]]
a + 4 | [a[4]] | end of array
a + 5 | [a[5]] | &a+1 points to this; accessing past array when dereferenced
This means that the code is subtracting a from &a[5] (or a+5), giving 5.
Note that this is undefined behavior, and should not be used under any circumstances. Do not expect the behavior of this to be consistent across all platforms, and do not use it in production programs.
Hmm, I suspect this is something that would not have worked back in the early days of C. It is clever though.
Taking the steps one at a time:
&a gets a pointer to an object of type int[5]
+1 gets the next such object assuming there is an array of those
* effectively converts that address into type pointer to int
-a subtracts the two int pointers, returning the count of int instances between them.
I'm not sure it is completely legal (in this I mean language-lawyer legal - not will it work in practice), given some of the type operations going on. For example you are only "allowed" to subtract two pointers when they point to elements in the same array. *(&a+1) was synthesised by accessing another array, albeit a parent array, so is not actually a pointer into the same array as a.
Also, while you are allowed to synthesise a pointer past the last element of an array, and you can treat any object as an array of 1 element, the operation of dereferencing (*) is not "allowed" on this synthesised pointer, even though it has no behaviour in this case!
I suspect that in the early days of C (K&R syntax, anyone?), an array decayed into a pointer much more quickly, so the *(&a+1) might only return the address of the next pointer of type int**. The more rigorous definitions of modern C++ definitely allow the pointer to array type to exist and know the array size, and probably the C standards have followed suit. All C function code only takes pointers as arguments, so the technical visible difference is minimal. But I am only guessing here.
This sort of detailed legality question usually applies to a C interpreter, or a lint type tool, rather than the compiled code. An interpretter might implement a 2D array as an array of pointers to arrays, because there is one less runtime feature to implement, in which case dereferencing the +1 would be fatal, and even if it worked would give the wrong answer.
Another possible weakness may be that the C compiler might align the outer array. Imagine if this was an array of 5 chars (char arr[5]), when the program performs &a+1 it is invoking "array of array" behaviour. The compiler might decide that an array of array of 5 chars (char arr[][5]) is actually generated as an array of array of 8 chars (char arr[][8]), so that the outer array aligns nicely. The code we are discussing would now report the array size as 8, not 5. I'm not saying a particular compiler would definitely do this, but it might.
I'm currently learning C Programming through Dan Gookin's book Beginning C Programming for Dummies.
One of the topic I'm currently reading is on the fact that arrays are in fact pointers. Dan attempted to prove that with the following code:
#include <stdio.h>
int main()
{
int numbers[10];
int x;
int *pn;
pn = numbers; /* initialize pointer */
/* Fill array */
for(x=0;x<10;x++)
{
*pn=x+1;
pn++;
}
pn = numbers;
/* Display array */
for(x=0;x<10;x++)
{
printf("numbers[%d] = %d, address %p\n",
x+1,*pn,pn);
pn++;
}
return(0);
}
My question is really with line 17. I realized that if I do not reintialize the pointer again as in line 17, the peek values of pointer pn being displayed at the second for loop sequence are a bunch of garbage that do not make sense. Therefore, I would like to know why is there a need to reintialize the pointer pn again for the code to work as intended?
An array is not a pointer, but C allows you to assign the array to a pointer of the type of the variable of the array, with the effect that that pointer will point to the first item in the array. That's what pn = numbers does.
pn is a pointer to an int, not to an array. It points to a single integer. When you increment the pointer, it just shifts to the next memory location. The shift it makes is the size of the type of the pointer, so int in this case.
So what does this prove? Not that an array is a pointer, but only that an array is a continuous block of memory that consists of N times the size of the type of your array item.
When you run the second loop, your pointer arrives at a piece of memory that doesn't belong to the array anymore, and so you get 'garbage' which is just the information which happens to exist at that location.
If you want to iterate over the array again by incrementing a pointer, you will have to reinitialize that pointer to the first item. The for loop does only do one thing, which is counting to 10. It doesn't know about the array and it doesn't know about the pointer, so the loop isn't going to automatically reset the pointer for you.
Since pn is incremented in the first loop, after the first loop is finished, pn will point to an address beyond the numbers array. Therefore, you must initialize pn to the beginning of the array before the second loop since you use the same pointer for printing the contents.
Because you have changed the address contained in pn in the statement pn++ in the following code snippet.
for(x=0;x<10;x++)
{
*pn=x+1;
pn++;
}
The pn pointer is being used to point into the numbers array.
The first for-loop uses pn to set the values, stepping pn throught the data element by element. After the end of the loop, pn points off the end of numbers (at a non-allocated 11th element).
For the second for-loop to work, i.e. to use pn to loop through numbers again by stepping through the array, pn needs to be moved to the front of the numbers array, otherwise you'll access memory that you shouldn't be looking at (non-allocated memory).
First arrays are not pointers. They decay to pointers when used in function calls and can be used (almost) the same.
Some subtle differences
int a[5]; /* array */
int *pa = a; /* pointer */
pa[0] = 5;
printf("%d\n", a[0]); /* ok it is the same here */
printf("address of array %p - address of pointer %p, value of pointer\n",
&a, &pa, pa); /* &a is the same as pa not &pa */
printf("size of array %d - size of pointer %d\n", sizeof(a), sizeof(pa));
sizeof(a) is here 5 * sizeof(int) whereas sizeof(pa) is the size of a pointer.
Now for your question:
After first loop, pn points to p[10] and no longer to p[0]. That's the reason why you must reset it.
Just to drive the point home, arrays are not pointers. When you declare numbers as int numbers[10], you get the following in memory:
+---+
numbers: | | numbers[0]
+---+
| | numbers[1]
+---+
...
+---+
| | numbers[9]
+---+
There's no storage set aside for a separate pointer to the first element of numbers. What happens is that when the expression numbers appears anywhere, and it isn't the operand of the sizeof or unary & operators, it is converted ("decays") to an expression of type "pointer to int", and the value of the expression is the address of the first element of the array.
What you're doing with pn is setting it to point to the first element of numbers, and then "walking" through the array:
+---+
numbers: | | <------+
+---+ |
| | |
+---+ |
... |
+---+ |
| | |
+---+ |
... |
|
+---+ |
pn: | | -------+
+---+
The expression pn++ advances pn to point to the next integer object, which in this case is the next element of the array:
+---+
numbers: | |
+---+
| | <------+
+---+ |
... |
+---+ |
| | |
+---+ |
... |
|
+---+ |
pn: | | -------+
+---+
Each pn++ advances the pointer until, at the end of the first loop, you have the following:
+---+
numbers: | |
+---+
| |
+---+
...
+---+
| |
+---+
... <------+
|
+---+ |
pn: | | -------+
+---+
At this point, pn is pointing to the object immediately following the end of the array. This is why you have to reset pn before the next loop; otherwise you're walking through the memory immediately following numbers, which can contain pretty much anything, including trap representations (i.e., bit patterns that don't correspond to a legal value for the given type).
Trying to access memory more than one past the end of an array invokes undefined behavior, which can mean anything from your code crashing outright to displaying garbage to working as expected.
During the fill array, the pointer pn is incremented and the data is placed on array. Same pointer variable used to print the array content. Since this reinitialise is done.
Kinda of a noob so don't kill me here.
What's the difference between the following codes?
int *p; //As i understand, it creates a pointer to an variable of size int.
int *p[100]; //Don't really know what this is.
int (*p)[100]; // I have come to understand that this is a pointer to an array.
This is a pointer to an int:
int *p;
┌────┐
│int*│
└────┘
It should point at an int, something like this:
┌────┐
│int*│
└─┃──┘
▼
┌───┐
│int│
└───┘
This is an array of 100 pointers to int:
int *p[100];
That is, it gives you 100 pointers.
┌────┬────┬────┬────┬────┬────┬┄
│int*│int*│int*│int*│int*│int*│
└────┴────┴────┴────┴────┴────┴┄
Each pointer should point an int, perhaps like this:
┌────┬────┬────┬────┬────┬────┬┄
│int*│int*│int*│int*│int*│int*│
└─┃──┴─┃──┴─┃──┴─┃──┴─┃──┴─┃──┴┄
▼ ▼ ▼ ▼ ▼ ▼
┌───┐┌───┐┌───┐┌───┐┌───┐┌───┐┌┄
│int││int││int││int││int││int││
└───┘└───┘└───┘└───┘└───┘└───┘└┄
Of course, there's no reason they can't all point at the same int, or whatever.
You may want to use an array of pointers if you want many pointers that you can easily
iterate over. You may, for example, dynamically allocate objects and have each pointer point at a different object:
p[0] = new int(0);
p[1] = new int(0);
// ...
Perhaps dynamically allocating ints isn't the best example, but I think the point is clear.
This is a pointer to an array of 100 int:
int (*p)[100];
That is, it gives you just 1 pointer:
┌───────────┐
│int(*)[100]│
└───────────┘
It should point at an array that contains 100 ints:
┌───────────┐
│int(*)[100]│
└─┃─────────┘
▼
┌───┬───┬───┬───┬───┬───┬┄
│int│int│int│int│int│int│
└───┴───┴───┴───┴───┴───┴┄
You will get a pointer to an array when you use the address-of operator (&) on the name of an array. For example:
int arr[100] = { /* some initial values */ };
int (*p)[100] = &arr;
Here, I've taken the address of the arr array, which gives me a pointer to that array. If you then want to access an element of the array, you have to dereference the pointer first: (*p)[3] will access element 3.
Side note:
Always remember that arrays are not pointers. As we have just seen, we can take the address of an array to get a pointer to it, just like any other (non-temporary) object in C++. The only special connection between arrays and pointers is that the name of an array can be implicitly converted to a pointer to the array's first element. That means the following is valid:
int arr[100] = { /* some initial values */ };
int* p = arr;
The pointer p will point at the first element in arr. Note that p is not a pointer to the array, but a pointer to an element of the array.
(Also note that there is no such thing as an array type function argument. If you write something like int p[] as a function argument, it is transformed by the compiler to be a int*.)
Sounds like you could use an introduction to the Spiral Rule.
Start at the variable and "spiral" your way around right to left:
+-------+
| +--+ | // So we have:
| | | | p // p
int * p | | * p // p is a pointer
^ ^ | | int * p // p is a pointer to an int
| +----+ |
+----------+
Next one:
+--------+
| +--+ | p // p
| | V | p[100] // p is an array of 100
int * p[100] | * p[100] // p is an array of 100 pointers
^ ^ | | int * p[100] // p is an array of 100 pointers to ints
| +----+ |
+-----------+
Finally, a new part of the rule, do anything in parenthesis first:
+-----+
| +-+ |
| ^ | | ( p) // p
int (* p) [100]; (*p) // p is a pointer
^ ^ | | (*p)[100] // p is a pointer to an array of 100
| +---+ | int (*p)[100] // p is a pointer to an array of 100 ints
+---------+
If you're online/have access to a computer, it's always usefule to use the cdecl.org site, but it's important to be able to read code offline as well, and this rule will let you do just that.
Kinda of a noob so don't kill me here
Working out what a type means in C can be tricky even for experts. No worries.
What's the difference between the following codes?
The other answers are good and I have no intention of contradicting them. Rather, here's yet another way to think about it. We need to define three things:
A variable is a thing that supports three operations. The fetch operation takes a variable and produces its current value. The fetch operation has no notation; you simply use the variable. The store operation takes a variable and a value, and stores the value in the variable. The address operation takes a variable and produces a pointer.
A pointer is a thing that supports one operation. The dereference operation, written as prefix *pointer, takes a pointer and produces a variable. (Pointers support other operations such as arithmetic and indexing -- which is a form of arithmetic -- but let's not go there.)
An array is a thing that supports one operation. The index operation takes an array and an integer and produces a variable. It's syntax is postfix: array[index]
OK, so now we come to your question. What does the declaration
int p;
mean? That the expression p is a variable of type int. Note that this is a variable; you can store things to p. What does the declaration
int *p;
mean? That the expression *p is a variable of type int. Now that we know that we can deduce what p is. Since *p is a dereference and produces a variable, p must be a pointer to int. What does the declaration
int *p[100];
mean? It means that *the expression *p[i] is a variable of type int provided that i is an integer value from 0 to 99. We got a variable out, but we could have gotten there from either the pointer or the array, so we have to figure out which. We consult the operator precedence table and discover that the indexing operator binds "tighter" than the dereferencing operator. That is:
*p[i]
is the same thing as
*(p[i])
and remember, that thing is a variable of type int. The contents of the parens are dereferenced to produce a variable of type int so the contents of the parens must be a pointer to int. Therefore
p[i]
is a pointer to int. How is that possible? This must be a fetch of a variable of type pointer-to-int! So p[i] is a variable of type pointer to int. Since this is an index operation, p must be an array of pointers to int.
Now you do the next one.
int (*p)[100];
means what?
int *p; --> Declares a pointer to an integer type.
int *p[100]; -->Declares an array of 100 pointers to integer type.
int (*p)[100]; --> Declares a pointer to an array of 100 integers.
Use cdecl for translating such types.
This question already has answers here:
How come an array's address is equal to its value in C?
(6 answers)
Address of an array
(3 answers)
Closed 9 years ago.
#include <stdio.h>
#include <stdlib.h>
#include <conio.h>
#include <string.h>
struct BOOK{
char name[15];
char author[33];
int year;
};
struct BOOK *books;
int main(){
int i,noBooks;
noBooks=2;
books=malloc(sizeof(struct BOOK)*noBooks);
books[0].year=1986;
strcpy(books[0].name,"MartinEden");
strcpy(books[0].author,"JackLondon");
//asking user to give values
scanf("%d",&books[1].year);
scanf("%s",&books[1].name);
scanf("%s",books[1].author);
printf("%d %s %s\n",books[0].year,books[0].author,books[0].name);
printf("%d %s %s\n",books[1].year,books[1].author,books[1].name);
getch();
return 0;
}
I give 1988 theidiotanddostoyevski
the output is
1986 JackLondon MartinEden
1988 dostoyevski theidiot
in scanf, in books[].name i used &, in books[].author I did not use but still it did same. For year it did not work. & is useless in structure?
I mean here
scanf("%d",&books[1].year);
scanf("%s",&books[1].name);
scanf("%s",books[1].author); //no & operator
char name[15];
char author[33];
here, i can use
char *name[15];
char *author[33];
nothing changes. why i cant see the difference?
The name member of the BOOK structure is a char array of size 15. When the name of the array is used in an expression, its value is the address of the array's initial element.
When you take an address of the name member from a struct BOOK, though, the compiler returns the base address of the struct plus the offset of the name member, which is precisely the same as the address of name's initial element. That is why both &books[1].name and books[1].name expressions evaluate to the same value.
Note: you should specify the size of the buffers into which you are going to read the strings; this will prevent potential buffer overruns:
scanf("%14s", books[1].name);
scanf("%32s", books[1].author);
This form is valid:
scanf("%s",books[1].author);
This form is invalid:
scanf("%s", &books[1].author);
s conversion specifier expects an argument of type pointer to char in scanf function, which is true in the first statement but false in the second statement. Failing to meet this requirement makes your function call invoke undefined behavior.
In the first statement, the trailing argument (after conversion) is of type pointer to char and in the second statement, the argument is of type pointer to an array 33 of char.
Except when it is the operand of the sizeof or unary & operator, or is a string literal being used to initialize another array in a declaration, an expression of type "N-element array of T" will be converted ("decay") to an expression of type "pointer to T", and the value of the expression will be the address of the first element in the array.
When you write
scanf("%s", books[1].author);
the expression books[i].author has type "33-element array of char". By the rule above, it will be converted to an expression of type "pointer to char" (char *) and the value of the expression will be the address of the first element of the array.
When you write
scanf("%s", &books[1].name);
the expression books[1].name is an operand of the unary & operator, so the conversion doesn't happen; instead, the type of the expression &books[1].name has type "pointer to 15-element array of char" (char (*)[15]), and its value is the address of the array.
In C, the address of the array and the address of the first element of the array are the same, so both expressions result in the same value; however, the types of the two expressions are different, and type always matters. scanf expects the argument corresponding to the %s conversion specifier to have type char *; by passing an argument of type char (*)[15], you invoke undefined behavior, meaning the compiler isn't required to warn you about the type mismatch, nor is it required to handle the expression in any meaningful way. In this particular case, the code "works" (gives you the result you expect), but it isn't required to; it could just as easily have caused a crash, or led to corrupted data, depending on the specific implementation of scanf.
Both calls should be written as
scanf("%s", books[1].name);
scanf("%s", books[1].author);
Edit
In answer to your comment, a picture may help. Here's what your books array would look like:
+---+ +---+
| | | name[0]
| +---+
| | | name[1]
| +---+
| ...
| +---+
| | | name[14]
| +---+
books[0] | | author[0]
| +---+
| | | author[1]
| +---+
| ...
| +---+
| | | author[33]
| +---+
| | | year
+---+ +---+
| | | name[0] <------ books[1].name
| +---+
| | | name[1]
| +---+
| ...
| +---+
| | | name[14]
| +---+
books[1] | | author[0] <------ books[1].author
| +---+
| | | author[1]
| +---+
| ...
| +---+
| | | author[33]
| +---+
| | | year
+---+ +---+
Each element of the books array contains two arrays plus an integer. books[1].name evaluates to the address of the first element of the name array within books[1]; similarly, the expression books[1].author evaluates to the address of the first element of the author array within books[1].