3

i'm trying to solve exercise 1 from chapter 2 of Effective C, it says:

"add a retrieve function to the counting example from listing 2-6 to retrieve the current value of counter"

the code from listing 2-6 is:

#include <stdio.h>

void increment(void) {
    static unsigned int counter;
    counter++;
    printf("%d ", counter);
}

int main(void) {
    for (int i = 0; i < 5; i++) {
        increment();
    }
    return 0;
}

I have tried a couple of things and failed, i don't understand how can one retrieve the value of counter, since outside of the increment function its out of scope and there is no pointer that can be used.

1
  • 8
    By defining the function as unsigned increment(void) and return counter; You can then move its output to the caller, with printf("%u ", increment()); Commented Dec 31, 2020 at 14:48

3 Answers 3

5

I'd separate the counter and the functions that retrieve or update its value. For that purpose, I'd transfer the counter to file scope and make it invisible (i.e. static) to other translation units:

static unsigned int counter;

void increment(void) {
    counter++;
}

unsigned int getCounter() {
    return counter;
}


// usually in a separate translation unit
int main(void) {
    for (int i = 0; i < 5; i++) {
        increment();
        printf("%d ", getCounter());
        
    }
    return 0;
}
Sign up to request clarification or add additional context in comments.

Comments

1

I've been reading Effective C just to refresh myself, and this question caught me a bit off guard because of how loose it is.

You could just elevate the scope of counter, but that defeats the purpose and isn't universally applicable. I think a good solution doesn't change the scope of counter. If we could access counter, then a retrieve function would be redundant. In practice, just return the damn value from increment() and capture it, there's no reason not to do that. But this exercise is to get us thinking about scope, so let's try to solve this without modifing either the scope of counter or the return type of increment.

One idea is to create a helper function that we can call in increment() to pass the value of counter. We can save that value in another static local variable. We can write an if statement so that if we give it a value that is out of counter's range (say, a negative number), it will return the last saved value of counter. This is pretty easy to understand, but it is naive. In giving our helper function two kinds of variables (counter is unsigned, a negative int is inherently signed), we have to either cast a signed value as unsigned (BAD idea), or cast an unsigned value as signed.

Capturing every arg as a signed int is less bad since it preserves the negative values, but this is a bit of a headache to handle. We would have to handle overflow, and we would be essentially wasting storage by limiting the max unsigned value in favor of being able to represent ALL negative ints (of which we really only care about 1). We could pick our "special" value to be the maximum unsigned int, and that's a safe bet as long as we handle the collision of counter and our special value. I think there's a better solution that is clear, mostly safe, and doesn't waste our time on dealing with converting types.

Let's instead make a helper function that takes in a pointer to an unsigned int. Instead of having a "special" value, we will have a special memory address. If what we pass in points to that address, return the count. Otherwise, assign count to the referenced value of our arg. We can set a file scoped unsigned int variable of any value, call this function and pass the address to counter into this within increment(), and then have retrieve call this function with the address of our "global" variable.

However, this ONLY works because counter is defined as static. Doing this to a non-static local variable is clear cut UB, but since static local variables live beyond their function call, this is not UB. It is, however, risky. As long as we don't assign our arg to anything, there's no other way to cause UB. The good news is that this preserves the scope of counter, is legible, and narrowly avoids UB.

#include <stdio.h>

unsigned int check = 1;
unsigned int setcounter(unsigned int *c){
    static unsigned int count = 0;
    if (c == &check){
        return count;
    }else{
        count = *c;
        return count;
    }
}
unsigned int retrieve(void){
    return setcounter(&check);
}
void increment(void){
    static unsigned int counter = 0;
    counter++;
    setcounter(&counter);
    printf("%d ", counter);
}

int main(void){
    for (int i = 0; i < 5; i++){
        printf("Count: ");
        increment();
        printf("Retrieved: ");
        printf("%d\n", retrieve());
    }
    return 0;
}

Anyhow, I'd imagine this is what Seacord intended for this exercise. C is not my primary language at all, so please correct me if I've misstated anything or committed any C crimes here. This compiled with -Wall -Werror -pedantic, which makes me happy.

Comments

0

With return you can do this like,

#include <stdio.h>

unsigned int increment(void)
{
    static unsigned int counter;
    counter++;
    return counter;
}

int main(void)
{
    for (int i = 0; i < 5; i++)
    {
        printf("%u ", increment());
    }
    return 0;
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.