tags:

views:

236

answers:

8
+4  Q: 

good practice in C

hello everyone I have this snippet of the code

#include<stdio.h>
#include<stdlib.h>
#include<string.h>

typedef struct Date {
    int date;
    char* month;
    int year;

} Date_t;

typedef Date_t* pDate_t;

void assignMonth(pDate_t birth)
{
    //1)
    birth->month = "Nov";

    //2)
    //birth->month = malloc(sizeof(char) * 4);
    //birth->month = strcpy(birth->month, "Nov");

}

int main()
{
    Date_t birth;
    birth.date = 13;
    assignMonth(&birth);
    birth.year = 1969;


    printf("%d %s %d\n",birth.date, birth.month, birth.year);
    return 0;
}

in function assignMonth I have two possibilies for assigning month, both give me the same result in the output, what is the difference between them, I think that second variant is the good one, am I wrong? if yes why? if not why? thanks in advance for any help

P.S. I'm interested in what is going on in my memory in both cases
+1  A: 

In the first case your cannot do something like birth->month[i]= 'c'. In other words you cannot modify the string literal "Mov" pointed to by birth->month because it is stored in the read only section of memory.

In the second case you can modify the contents of p->month because "Mov" resides on the heap. Also you need to deallocate the allocated memory using free in this case.

Prasoon Saurav
+9  A: 

It depends on what you want to do with birth.month later. If you have no intention of changing it, then the first is better (quicker, no memory cleanup requirement required, and each Date_t object shares the same data). But if that is the case, I would change the definition of month to const char *. In fact, any attempt to write to *birth.month will cause undefined behaviour.

The second approach will cause a memory leak unless you remember to free(birth.month) before birth goes out of scope.

Oli Charlesworth
@sje397: No. `char * const` declares an immutable pointer. `const char *` declares a pointer to an immutable object.
Oli Charlesworth
@Oli - yeah sorry...mental short circuit.
sje397
+1  A: 

With option 1 you never allocate memory to store "Nov", which is okay because it's a static string. A fixed amount of memory was allocated for it automatically. This will be fine so long as it's a string that appears literally in the source and you never try to modify it. If you wanted to read a value in from the user, or from a file, then you'd need to allocate first.

Sorpigal
A: 

Seperate to your question; why is struct Date typedefed? it has a type already - "struct Date".

You can use an incomplete type if you want to hide the structure decleration.

In my experience, people seem to typedef because they think they should - without actually thinking about the effect of doing so.

Blank Xavier
The reason for typedef-ing structs is the same as for any typedef; to save the amount of typing (no pun intended) required, and to provide a level of abstraction (on a purely syntatical level). I've heard the argument made that it's "bad practice" to hide a struct behind a typedef, but I've never heard a good justification for it.
Oli Charlesworth
Save on typing? no offense, but I think that ridiculous. It amounts to saving the word 'struct'. If that's the case, why not use #defines to shorten long keywords in C?
Blank Xavier
The abstraction argument is that which I think is generally the reason given for the use of a typedef. I think that abstraction is however almost not honoured - for if abstraction is required, then the underlying type *cannot* be known. This means you cannot, for example, use any comparison operators - because to do so is to assume a type which can be compared. As such, ALL operations on a typedef must occur through functions. I *NEVER* see this occur; and if this is not done, then the typedef is harmful, because it obscures the type, *while still requiring the reader to know the type*.
Blank Xavier
@Blank: Why would I want to clutter my variable/argument definitions everywhere with `struct` when I don't have to? Your argument about abstraction applies to essentially all uses of `typedef` (e.g. in order to use a function-pointer `typedef`, you need to know it's a function. In order to safely do comparisons on a `float` `typedef`, you need to know it's a `float`. And so on.)
Oli Charlesworth
+5  A: 

You're correct, the second variant is the "good" one.

Here's the difference:

With 1, birth->month ends up pointing to the string literal "Nov". It is an error to try to modify the contents of birth->month in this case, and so birth->month should really be a const char* (many modern compilers will warn about the assignment for this reason).

With 2, birth->month ends up pointing to an allocated block of memory whose contents are "Nov". You are then free to modify the contents of birth->month, and the type char* is accurate. The caveat is that you are also now required to free(birth->month) in order to release this memory when you are done with it.

The reason that 2 is the correct way to do it in general, even though 1 seems simpler in this case, is that 1 in general is misleading. In C, there is no string type (just sequences of characters), and so there is no assignment operation defined on strings. For two char*s, s1 and s2, s1 = s2 does not change the string value pointed to by s1 to be the same as s2, it makes s1 point at exactly the same string as s2. This means that any change to s1 will affect the contents of s2, and vice-versa. Also, you now need to be careful when deallocating that string, since free(s1); free(s2); will double-free and cause an error.

That said, if in your program birth->month will only ever be one of several constant strings ("Jan", "Feb", etc.) variant 1 is acceptable, however you should change the type of birth->month to const char* for clarity and correctness.

Tyler McHenry
For (1), would "Nov" stay in scope? I'd have thought this would be open to being overwritten by another method (although this example only has this one method) as the function goes out of scope (and thus any memory it's using could be reused). Or because this is a static string, is this okay? Or is this compiler dependent, and different compilers would do different things? I've always assumed that any variable or constant of any type (int, pointer, char, whatever) in a method will be void once a function exits, unless it's been declared as 'static'?
Chris J
String literals never go out of scope because they are not stored on the stack (automatic storage duration). It is mandated by the C standard that string literals have static storage duration, which means that they are essentially the same as a global constant `char` array, or a constant `char` array declared with `static` within a function. What's compiler-defined is whether two identical string literals refer to the *same* global string, i.e. you aren't guaranteed that `"Nov"` in `foo()` and `"Nov"` in `bar()` have the same address.
Tyler McHenry
Which C standard mandates static storage on string literals? C89?
Blank Xavier
@Blank Xavier: Yes. §3.1.4 Semantics in C89 starts off: *"A character string literal has static storage duration and type ``array of char ,''..."*
caf
Thanks very much.
Blank Xavier
+2  A: 

I suggest either:

const char* month;
...
birth->month = "Nov";

or:

char month[4];
...
strcpy(birth->month, "Nov");

avoiding the memory allocation altogether.

Clifford
A: 

For this example, it doesn't matter too much. If you have a lot of Date_t variables (in, say, an in-memory database), the first methow will lead to less memory usage over-all, with the gotcha that you should not, under any circumstances, change any of the characters in the strings, as all "Nov" strings would be "the same" string (a pointer to the same 4 chars).

So, to an extent, both variants are good, but the best one would depend on expected usage pattern(s).

Vatine
+1  A: 

Neither is correct. Everyone's missing the fact that this structure is inherently broken. Month should be an integer ranging from 1 to 12, used as an index into a static const string array when you need to print the month as a string.

R..
Then, `month` should be declared as an `enum` instead, to provide better control over data validation.
Hemant
I agree, but 'broken' is perhaps too strong. Poorly designed and ill-thought out perhaps.
Clifford