Expert C Programming: Deep C Secrets

Category: Programming
Author: Peter Van der Linden
4.1
All Stack Overflow 23
This Year Stack Overflow 2
This Month Stack Overflow 5

Comments

by roganp   2022-08-16
Expert C Programming: Deep C Secrets https://www.amazon.com/Expert-Programming-Peter-van-Linden/d...

Entertaining and informative. Highly recommended.

by ktr   2021-03-03
I really enjoyed Expert C Programming (https://www.amazon.com/Expert-Programming-Peter-van-Linden/d...).
by anonymous   2019-07-21

You might find "expert c programming" a good read - unpacking this kind of thing is in one of the chapters, if I remember right. It's a long time since I read it, but I remember thinking it was worth the effort at the time. http://www.amazon.com/Expert-Programming-Peter-van-Linden/dp/0131774298

by Johan   2019-07-21

As stated above I don't think the order is important, but this is the order I wished someone would have showed me the stuff.

  1. Arrays
  2. Pointers
  3. How Arrays and Pointers are the same
  4. Why Arrays and Pointers are NOT the same

For more info on point 4 I really recommend chapter 4 "The Shocking truth: C arrays and Pointers Are NOT the Same!" in "Expert C, deep C secrets".

/Johan


Update:

Some links to the book, and there is also a preview of the book. http://books.google.se - Expert C, deep C secrets

And the user comments about this book is true: http://www.amazon.co.uk/Expert-Programming-Peter-van-Linden/dp/0131774298

by anonymous   2019-07-21

(For C) Expert C Programming: Deep C secrets without a doubt.

by anonymous   2019-07-21

About how to use pointers and the difference between array and pointer, I recommend you read the "expert c programming" (http://www.amazon.com/Expert-Programming-Peter-van-Linden/dp/0131774298/ref=sr_1_1?ie=UTF8&qid=1371439251&sr=8-1&keywords=expert+c+programming).

by anonymous   2019-07-21

The best source for learning the complexities of C is the book Expert C Programming by Peter van der Linden (http://www.amazon.co.uk/Expert-Programming-Peter-van-Linden/dp/0131774298).

The name of the book is misleading because it's very easily read by beginners I think.

by bello   2018-01-24
I really enjoyed "Deep C Secrets" as an intermediate C book:

Expert C Programming: Deep C Secrets https://www.amazon.com/dp/0131774298/

A little dated, still lots of relevant knowledge though.

by anonymous   2017-08-20

You must read Expert C Programming by Peter van der Linden.

alt text

by AviewAnew   2017-08-20

Reference Style - All Levels

Beginner

Intermediate

Above Intermediate

Uncategorized Additional C Programming Books

  • Essential C (Free PDF) - Nick Parlante
  • The new C standard - an annotated reference (Free PDF) - Derek M. Jones
by D.Shawley   2017-08-20

I'm not sure what you are trying to do but the assignment of a pointer value to an array is what's bothering the compiler as mentioned by Charlie. I'm curious about checking against the NUL character constant '\0'. Your sample array is uninitialized memory so the comparison in arrayGen isn't going to do what you want it to do.

The parameter list that you are using ends up being identical to:

int* arrayGen(int *arrAddr, int maxNum)

for most purposes. The actual statement in the standard is:

A declaration of a parameter as "array of type" shall be adjusted to "qualified pointer to type", where the type qualifiers (if any) are those specified within the [ and ] of the array type derivation. If the keyword static also appears within the [ and ] of the array type derivation, then for each call to the function, the value of the corresponding actual argument shall provide access to the first element of an array with at least as many elements as specified by the size expression.

If you really want to force the caller to use an array, then use the following syntax:

void accepts_pointer_to_array (int (*ary)[10]) {
    int i;
    for (i=0; i<10; ++i) {
        (*ary)[i] = 0; /* note the funky syntax is necessary */
    }
}

void some_caller (void) {
    int ary1[10];
    int ary2[20];
    int *ptr = &ary1[0];
    accepts_pointer_to_array(&ary1); /* passing address is necessary */
    accepts_pointer_to_array(&ary2); /* fails */
    accepts_pointer_to_array(ptr);   /* also fails */
}

Your compiler should complain if you call it with anything that isn't a pointer to an array of 10 integers. I can honestly say though that I have never seen this one anywhere outside of various books (The C Book, Expert C Programming)... at least not in C programming. In C++, however, I have had reason to use this syntax in exactly one case:

template <typename T, std::size_t N>
std::size_t array_size (T (&ary)[N]) {
    return N;
}

Your mileage may vary though. If you really want to dig into stuff like this, I can't recommend Expert C Programming highly enough. You can also find The C Book online at gbdirect.

by anonymous   2017-08-20

Let me start off by saying something a little off topic:

Anyway, it looks like you're looking at the extra credit exercises from this chapter.

  • Another aside- I don't think this is an especially sensible exercise for learning (another answer pointed out the question isn't formed to make sense), so this discussion is going to get a little complex. I would instead recommend the exercises from Chapter 5 of K & R.

First we need to understand that pointers are not the same as arrays. I've expanded on this in another answer here, and I'm going to borrow the same diagram from the C FAQ. Here's what's happening in memory when we declare an array or a pointer:

 char a[] = "hello";  // array

   +---+---+---+---+---+---+
a: | h | e | l | l | o |\0 |
   +---+---+---+---+---+---+

 char *p = "world"; // pointer

   +-----+     +---+---+---+---+---+---+
p: |  *======> | w | o | r | l | d |\0 |
   +-----+     +---+---+---+---+---+---+

So, in the code from the book, when we say:

int ages[] = {23, 43, 12, 89, 2};

We get:

      +----+----+----+----+---+
ages: | 23 | 43 | 12 | 89 | 2 |
      +----+----+----+----+---+

I'm going to use an illegal statement for the purpose of explanation - if we could have said:

int *ages = {23, 43, 12, 89, 2}; // The C grammar prohibits initialised array
                                 // declarations being assigned to pointers, 
                                 // but I'll get to that

It would have resulted in:

      +---+     +----+----+----+----+---+
ages: | *=====> | 23 | 43 | 12 | 89 | 2 |
      +---+     +----+----+----+----+---+

Both of these can be accessed the same way later on - the first element "23" can be accessed by ages[0], regardless of whether it's an array or a pointer. So far so good.

However, when we want to get the count we run in to problems. C doesn't know how big arrays are - it only knows how big (in bytes) the variables it knows about are. This means, with the array, you can work out the size by saying:

int count = sizeof(ages) / sizeof(int);

or, more safely:

int count = sizeof(ages) / sizeof(count[0]);

In the array case, this says:

int count = the number of bytes in (an array of 6 integers) / 
                 the number of bytes in (an integer)

which correctly gives the length of the array. However, for the pointer case, it will read:

int count = the number of bytes in (**a pointer**) /
                 the number of bytes in (an integer)

which is almost certainly not the same as the length of the array. Where pointers to arrays are used, we need to use another method to work out how long the array is. In C, it is normal to either:

  • Remember how many elements there were:

    int *ages = {23, 43, 12, 89, 2}; // Remember you can't actually
                                     // assign like this, see below
    int ages_length = 5;
    for (i = 0 ; i < args_length; i++) {
    
  • or, keep a sentinel value (that will never occur as an actual value in the array) to indicate the end of the array:

    int *ages = {23, 43, 12, 89, 2, -1}; // Remember you can't actually
                                         // assign like this, see below
    for (i = 0; ages[i] != -1; i++) {
    

    (this is how strings work, using the special NUL value '\0' to indicate the end of a string)


Now, remember that I said you can't actually write:

    int *ages = {23, 43, 12, 89, 2, -1}; // Illegal

This is because the compiler won't let you assign an implicit array to a pointer. If you REALLY want to, you can write:

    int *ages = (int *) (int []) {23, 43, 12, 89, 2, -1}; // Horrible style 

But don't, because it is extremely unpleasant to read. For the purposes of this exercise, I would probably write:

    int ages_array[] = {23, 43, 12, 89, 2, -1};
    int *ages_pointer = ages_array;

Note that the compiler is "decaying" the array name to a pointer to it's first element there - it's as if you had written:

    int ages_array[] = {23, 43, 12, 89, 2, -1};
    int *ages_pointer = &(ages_array[0]);

However - you can also dynamically allocate the arrays. For this example code, it will become quite wordy, but we can do it as a learning exercise. Instead of writing:

int ages[] = {23, 43, 12, 89, 2};

We could allocate the memory using malloc:

int *ages = malloc(sizeof(int) * 5); // create enough space for 5 integers
if (ages == NULL) { 
   /* we're out of memory, print an error and exit */ 
}
ages[0] = 23;
ages[1] = 43;
ages[2] = 12;
ages[3] = 89;
ages[4] = 2;

Note that we then need to free ages when we're done with the memory:

free(ages); 

Note that there are a few ways to write the malloc call:

 int *ages = malloc(sizeof(int) * 5);

This is clearer to read for a beginner, but generally considered bad style because there are two places you need to change if you change the type of ages. Instead, you can write either of:

 int *ages = malloc(sizeof(ages[0]) * 5);
 int *ages = malloc(sizeof(*ages) * 5);

These statements are equivalent - which you choose is a matter of personal style. I prefer the first one.


One final thing - if we're changing the code over to use arrays, you might look at changing this:

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

But, you don't need to. The reason why is a little subtle. First, this declaration:

char *argv[]

says "there is an array of pointers-to-char called argv". However, the compiler treats arrays in function arguments as a pointer to the first element of the array, so if you write:

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

The compiler will actually see:

int main(int argc, char **argv)

This is also the reason that you can omit the length of the first dimension of a multidimensional array used as a function argument - the compiler won't see it.

by anonymous   2017-08-20

It declares f as an array of pointers to function that returns int and take int * as an argument.

There is a precedence rule for understanding complex declarations which is discussed in the book Expert C Programming: Deep C Secrets:

The Precedence Rule for Understanding C Declarations

  • A. Declarations are read by starting with the name and then reading in precedence order.

  • B. The precedence, from high to low, is:

  • B.1. parentheses grouping together parts of a declaration

  • B.2. the postfix operators:
    parentheses () indicating a function, and
    square brackets [] indicating an array.

  • B.3. the prefix operator:
    the asterisk denoting "pointer to".

  • C. If a const and/or volatile keyword is next to a type specifier (e.g. int, long, etc.) it applies to the type specifier. Otherwise the const and/or volatile keyword applies to the pointer asterisk on its immediate left.

Therefore, it goes like:

      f                          -- f (A)
     f[]                         -- is an array (B.1)
    *f[]                         -- of pointers to (B.3)
   (*f[])( )                     -- function (B.2)
 (*f[])( int * )                 -- that expects a pointer to an int as an argument
int (*f[])( int* )               -- and return an int     

I would suggest to avoid spiral rule as it fails in some cases, for example in case of int* a[10][15];.

by anonymous   2017-08-20

I think this is because of the Maximal Munch Rule. From Wiki:

In computer programming and computer science, "maximal munch" or "longest match" is the principle that when creating some construct, as much of the available input as possible should be consumed.

From Expert C Programming:

The ANSI standard specifies a convention that has come to be known as the maximal munch strategy. Maximal munch says that if there's more than one possibility for the next token, the compiler will prefer to bite off the one involving the longest sequence of characters.

by jaredor   2017-08-20

Legacy Fortran Soapbox

I helped maintain/improve a legacy Fortran code base for quite a while and for the most part think sixlettervariables is on the money. That advice though, tends to the technical; a tougher row to hoe is in implementing "good practices".

  • Establish a required coding style and coding guidelines.
  • Require a code review (of more than just the coder!) for anything submitted to the code base. (Version control should be tied to this process.)
  • Start building and running unit tests; ditto benchmark or regression tests.

These might sound like obvious things these days, but at the risk of over-generalizing, I claim that most Fortran code shops have an entrenched culture, some started before the term "software engineering" even existed, and that over time what comes to dominate is "Get it done now". (This is not unique to Fortran shops by any means.)

Embracing Gotchas

But what to do with an already existing, grotty old legacy code base? I agree with Joel Spolsky on rewriting, don't. However, in my opinion sixlettervariables does point to the allowable exception: Use software tools to transition to better Fortran constructs. A lot can be caught/corrected by code analyzers (FORCHECK) and code rewriters (plusFORT). If you have to do it by hand, make sure you have a pressing reason. (I wish I had on hand a reference to the number of software bugs that came from fixing software bugs, it is humbling. I think some such statistic is in Expert C Programming.)

Probably the best offense in winning the game of Fortran gotchas is having the best defense: Knowing the language fairly well. To further that end, I recommend ... books!

Fortran Dead Tree Library

I have had only modest success as a "QA nag" over the years, but I have found that education does work, some times inadvertently, and that one of the most influential things is a reference book that someone has on hand. I love and highly recommend

Fortran 90/95 for Scientists and Engineers, by Stephen J. Chapman

The book is even good with Fortran 77 in that it specifically identifies the constructs that shouldn't be used and gives the better alternatives. However, it is actually a textbook and can run out of steam when you really want to know the nitty-gritty of Fortran 95, which is why I recommend

Fortran 90/95 Explained, by Michael Metcalf & John K. Reid

as your go-to reference (sic) for Fortran 95. Be warned that it is not the most lucid writing, but the veil will lift when you really want to get the most out of a new Fortran 95 feature.

For focusing on the issues of going from Fortran 77 to Fortran 90, I enjoyed

Migrating to Fortran 90, by Jim Kerrigan

but the book is now out-of-print. (I just don't understand O'Reilly's use of Safari, why isn't every one of their out-of-print books available?)

Lastly, as to the heir to the wonderful, wonderful classic, Software Tools, I nominate

Classical FORTRAN, by Michael Kupferschmid

This book not only shows what one can do with "only" Fortran 77, but it also talks about some of the more subtle issues that arise (e.g., should or should not one use the EXTERNAL declaration). This book doesn't exactly cover the same space as "Software Tools" but they are two of the three Fortran programming books that I would tag as "fun".... (here's the third).

Miscellaneous Advice that applies to almost every Fortran compiler

  • There is a compiler option to enforce IMPLICIT NONE behavior, which you can use to identify problem routines without modifying them with the IMPLICIT NONE declaration first. This piece of advice won't seem meaningful until after the first time a build bombs because of an IMPLICIT NONE command inserted into a legacy routine. (What? Your code review didn't catch this? ;-)
  • There is a compiler option for array bounds checking, which can be useful when debugging Fortran 77 code.
  • Fortran 90 compilers should be able to compile almost all Fortran 77 code and even older Fortran code. Turn on the reporting options on your Fortran 90 compiler, run your legacy code through it and you will have a decent start on syntax checking. Some commercial Fortran 77 compilers are actually Fortran 90 compilers that are running in Fortran 77 mode, so this might be relatively trivial option twiddling for whatever build scripts you have.
by anonymous   2017-08-20

One excellent book on "advanced" C programming is Peter van der Linden's Expert C Programming.

You even get an appendix with funny interview stories :)!

by unimpressive   2017-08-19
I never got into C because of books like "Expert C Programming"[0], knowing they exist tells me that theres a ton of "gotchas", and life is too short for that if I'm not really crazy about it in the first place.

Then again, as far as actual grammars go, I've heard C++ is bad enough that the compilers are the standard, and that if you want to be "compliant" with real world C++ code you copy every feature [1] of GCC.

[0]: http://www.amazon.com/Expert-Programming-Peter-van-Linden/dp...

[1]: ftp://ftp.trailing-edge.com/pub/rsx11freewarev2/rsx81b/374001/jargon.txt

by tjr   2017-08-19
C would be worth studying, as would some books around and about C. (Peter van der Linden's Expert C Programming comes to mind.) Even if you don't actually use C much, it's been a standard for a long time, with lots of interesting heritage.

http://www.amazon.com/Expert-Programming-Peter-van-Linden/dp...