0

I am taking the operating system class in mit online, I completed the first assignement http://ocw.mit.edu/courses/electrical-engineering-and-computer-science/6-828-operating-system-engineering-fall-2012/assignments/MIT6_828F12_assignment1.pdf
but what surprised me is how they return the data structures, they work with a data structure and they return a smaller data structure, and to use it they just cast it back. I see that it can optimize the code but is this safe ? is it good practice ?

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

struct small
{
    int  n ;
};

struct big
{
    int n ;
    char * string ;
};

struct small* f()
{
    struct big* x = malloc(sizeof(struct big));
    x->n = 'X';
    x->string = strdup("Nasty!");
    return (struct small*) x ;
}

int main(int argc, char *argv[])
{
    struct big *y = (struct big*)f();
    printf("%s\n",y->string);
}

EDIT 1 : here is the link from mit, i just replicated the idea in my own code. http://ocw.mit.edu/courses/electrical-engineering-and-computer-science/6-828-operating-system-engineering-fall-2012/assignments/sh.c

Baroudi Safwen
  • 803
  • 6
  • 17
  • 3
    I think apart from anything, this is bad code. – Sourav Ghosh Jan 19 '16 at 16:55
  • 3
    They don't return a structure but the *address* of one. I'm not sure this is even defined behavior -- I'd be happier if the cast was to and from a `void*`. But in C the numeric value of the address doesn't change from the cast, so it works. – Peter - Reinstate Monica Jan 19 '16 at 16:57
  • 2
    Maybe there's something I'm missing here, but I completely don't see the point. Returning structures in C is typically implemented by compilers by simply passing a hidden pointer to a `struct` allocated on the caller's stack, but that's not even the case here because they're shuffling around pointers. To me it just looks like (pointlessly, i.e. without any advantages) bad practice that should be avoided. And, everything else apart, this is undefined behavior because one can only convert to and from `void*`. – user4520 Jan 19 '16 at 16:59
  • i figured, but is it safe? isn't it possible in a bigger program, that another variable would corrupt the "string" memory ? – Baroudi Safwen Jan 19 '16 at 17:00
  • Where did you find this code? – Andrew Cheong Jan 19 '16 at 17:00
  • 2
    Not only is this awful, but unnecessary as well. It's returning a pointer, which is the same size no matter what it points to. This looks like an attempt at information hiding without C++'s `private` keyword. – John Sensebe Jan 19 '16 at 17:00
  • 1
    @szczurcio Passing and returning a structure in C (as soon as it was allowed, i.e. post-K&R) always had value semantics. No secret pointers there. – Peter - Reinstate Monica Jan 19 '16 at 17:01
  • They don't return a "data structure" (whatever that means), they return a pointer. I have no iidea why they might be doing this. This practice does not enable any optimisations. It is useful when one wants to pass different data types through a common-type pointer. If you only have one type to pass, just use a pointer to that type. – n. m. could be an AI Jan 19 '16 at 17:02
  • The "string memory" is located on the heap, that is maintained by the system via free/malloc functions. So unless you are doing something with the string field nothing can corrupt it. – gmoshkin Jan 19 '16 at 17:03
  • The code you post isn't in the link you provided. What do they actually **say** about the code? I notice they have used the string `"Nasty!"` so what does that imply? – Weather Vane Jan 19 '16 at 17:05
  • @AndrewCheong i wrote it to avoid copying/pasting 300 lines from the original code, i replicated the style. if you got the time you can chekc this file : http://ocw.mit.edu/courses/electrical-engineering-and-computer-science/6-828-operating-system-engineering-fall-2012/assignments/sh.c – Baroudi Safwen Jan 19 '16 at 17:05
  • It's an idiomatic way how to implement object-oriented design in C. I don't think it's "bad" coding style as such, just something the language was not designed for, so it may look "weird" for some of you. – kfx Jan 19 '16 at 17:08
  • Very pertinent to this discussion, in the true code from the link, we don't have small and big, but small, big1, big2 and big3, where all of the bign share the common element in small. – cdkMoose Jan 19 '16 at 17:10
  • As given, the question is not clear. You might have missed the actual point and/or oversimplificated the example - as I understand you do not use the original one. Anyway, the code as show is useless - at best - It is not UB, though, unless you access one `struct` as the other after first write after `malloc`, as that violates effective type/aliasing rules. – too honest for this site Jan 19 '16 at 17:12

3 Answers3

5
  1. No structure is returned, but a pointer to a structure. A pointer contains the address of the memory where the actual object is located. (In this case it has been allocated dynamically with malloc, hence has dynamic storage duration and will live until the pogram ends or a free is called on the pointer.)

  2. The code is legal and has defined semantics. 6.3.2.3/7 of the n1570 draft says

    A pointer to an object type may be converted to a pointer to a different object type. If the resulting pointer is not correctly aligned for the referenced type, the behavior is undefined. [does not apply here. -ps] Otherwise, when converted back again, the result shall compare equal to the original pointer.

Note that the structures could be completely unrelated (the first data element does not need to be the same type). The cast could even be to a built-in type like int.

The issue may be different if the object were accessed through a pointer of the wrong type (which it isn't) because of aliasing issues.

Peter - Reinstate Monica
  • 15,048
  • 4
  • 37
  • 62
  • ok it is legal, but what if my program called malloc and free many times, wouldn'it corrupt the memory reserved for the string variable ? – Baroudi Safwen Jan 19 '16 at 17:12
  • `malloc`/`free`are oblivious to data types (except that memory returned by malloc can hold the most demanding type alignment-wise). They only keep book of locations and the number of bytes starting there which need to be freed eventually. The *pointer* `string` to "Nasty" will not be affected by malloc and free. **But** you'll have to `free` the *memory* `string` points to because it has been allocated dynamically with `strdup`. – Peter - Reinstate Monica Jan 19 '16 at 17:16
  • I have a hunch that you still think the object is somehow "cut short" or "pruned" by the cast. I can assure you it is not affected ;-). – Peter - Reinstate Monica Jan 19 '16 at 17:18
  • i know casting does not cut short, thought allocating and freeing memory can do that. but now i see since it is allocated and not freed it will not be touched by another malloc. thank you for your time, thank you for your explanations. – Baroudi Safwen Jan 19 '16 at 17:27
  • As long as you do not access memory which was not allocated there is no danger. You can pass any type pointer to `free` -- its parameter is declared a `void *` anyway. That pointer type is not taken into consideration.`malloc` and `free` couldn't care less what you do with the memory and which pointers you use. The book-keeping of the object memory size is done behind the scenes (simply memorized in spaces between the allocated blocks, to be exact). (Note that the situation is wildly different in C++!) – Peter - Reinstate Monica Jan 19 '16 at 18:07
1

Yes it is safe, as long your compiler follows the C standard

Why doesn't GCC optimize structs?

But no, I do not consider this a good practice.

Good practices are relative, and should never be use as a rule. This construction is required if you want to simulate OO inheritance behavior with C.

Community
  • 1
  • 1
Jonatan Goebel
  • 1,107
  • 9
  • 14
1

To answer your questions:

Is it safe?

Obviously not. Just casting the type of a pointer to something else is something you should only do if you are positively sure it's the right type.

However, if you know that, everything is fine.

Is it good practice?

No, not in this shape.

You sometimes do Object Oriented Programming in C in a similar way (compare CPython's PyObject): You use one "base" object, and one "type" struct which have a structure like:

struct obj_type {
  const char* name;
  size_t length; // this is important so that you can copy the object later on without knowing its insides
};

struct obj_base {
  obj_type* type;
};

Because it is guaranteed that in C, pointers to structs point to the address of their first element, you can use further objects that build atop of that:

struct more_complex_object {
  obj_type* type; 
  int value;
};

...
int main() {
  obj_type *more_complex_object_type = malloc(sizeof(obj_type));
  more_complex_object_type->name = "A more complex object type";
  more_complex_object_type->length = sizeof(more_complex_object);
  more_complex_object *obj = malloc(more_complex_object_type->length);
  obj->type = more_complex_object_type;
  obj->value = 10;
  ...
  //let's now use it as a simple instance of the "super" object
  obj_base* just_an_object = (obj_base*)more_complex_object;
  //when we copy things, we make sure to copy the full length:
  obj_base* target = malloc(sizeof(more_complex_object));
  memcpy(target, just_an_object, just_an_object->type->length);

  //now, when handling an object, we can check its type
  if(just_an_object->type == more_complex_object_type) {
    more_complex_object *my_obj = (more_complex_object)just_an_object;
    printf("value: %d\n", my_obj->value);
  }
}
Marcus Müller
  • 34,677
  • 4
  • 53
  • 94
  • 1
    It's as safe as anything else in C. – Peter - Reinstate Monica Jan 19 '16 at 17:21
  • @PeterA.Schneider: yes, as long as you don't happen to cast something to something that is not actually a "compatible type" – Marcus Müller Jan 19 '16 at 17:25
  • you said it all, i appreciate your answer and i can't thank you enough Sir. while ( 1 ) { thank you ; } – Baroudi Safwen Jan 19 '16 at 17:29
  • Your example is, as far as I can tell, subject to aliasing issues. You are aware of the aliasing discussion *specifically* with respect to `PyObject`? Cf. https://mail.python.org/pipermail/python-dev/2003-July/036909.html. The thing is that there is no field of type `obj_base` in `more_complex_object`, which makes the cast `(obj_base*)more_complex_object` (provided the appropriate tyepdefs were in place to omit `struct`) an illegal alias. The types are not "compatible", in C standard lingo. Or am I misunderstanding that? – Peter - Reinstate Monica Jan 19 '16 at 17:52
  • An unrelated question -- did you ever work in Hildesheim and have a drawer full of non-standard USB plugs?? – Peter - Reinstate Monica Jan 19 '16 at 18:00
  • @PeterA.Schneider I appreciate the question, but no, I never worked in Hildesheim. And a have experience with heaps of non-standard-compliant USB host controllers, but I'd prefer not to have that :/ – Marcus Müller Jan 19 '16 at 18:17
  • @PeterA.Schneider: huh, good point, I wasn't aware of that specific problem. The thing is that the `PyObject` magic definitely is something that I'm always a bit afraid to go into very deeply, because I was suspecting something like "how the hell would gcc know it should not break this?"; however, "extending" structs is something not only done in CPython, but also all over the place in the Linux kernel, so I figured that's just how it is usually done. – Marcus Müller Jan 19 '16 at 18:19
  • You may be interested in Linus' rants about the gcc people then: https://lkml.org/lkml/2009/1/12/369 Money quote: "nyaah, nyaah, the standards people said we can do this".There seems to be a bit of a softening happening lately. – Peter - Reinstate Monica Jan 19 '16 at 18:22