Make your Data Type more Abstract with Opaque Types in C

Sdílet
Vložit
  • čas přidán 5. 04. 2021
  • Patreon ➤ / jacobsorber
    Courses ➤ jacobsorber.thinkific.com
    Website ➤ www.jacobsorber.com
    ---
    Make your Data Type more Abstract with Opaque Types in C // When you think about abstract data types (ADTs) you probably think about object-oriented languages, like C++, java, ruby, or python. But, even though C isn't object-oriented and doesn't have a private keyword, you can still limit data type visibility using opaque types, in the same way that LibC does with FILE pointers. This video shows you how.
    Related Videos:
    Queue Example: • Implementing a Circula...
    IFDEF guards: • Header Issues: Guards,...
    ***
    Welcome! I post videos that help you learn to program and become a more confident software developer. I cover beginner-to-advanced systems topics ranging from network programming, threads, processes, operating systems, embedded systems and others. My goal is to help you get under-the-hood and better understand how computers work and how you can use them to become stronger students and more capable professional developers.
    About me: I'm a computer scientist, electrical engineer, researcher, and teacher. I specialize in embedded systems, mobile computing, sensor networks, and the Internet of Things. I teach systems and networking courses at Clemson University, where I also lead the PERSIST research lab.
    More about me and what I do:
    www.jacobsorber.com
    people.cs.clemson.edu/~jsorber/
    persist.cs.clemson.edu/
    To Support the Channel:
    + like, subscribe, spread the word
    + contribute via Patreon --- [ / jacobsorber ]
    Source code is also available to Patreon supporters. --- [jsorber-youtube-source.heroku...]

Komentáře • 105

  • @tordjarv3802
    @tordjarv3802 Před 3 lety +22

    I have used opaque types in C for years, but I didn't know that they where called opaque types until today. Thanks

  • @ISKLEMMI
    @ISKLEMMI Před 3 lety +25

    Thank you for explaining not only what opaque types are and how to implement them, but also why one might want to use them. I've only just begun learning C, but you've helped me to appreciate C for what it is and to leave my OO baggage behind.

  • @mattwilliams6500
    @mattwilliams6500 Před 3 lety +78

    This is perfect thank you. Top of my OS class thanks to all these. We NEVER learn anything about writing good C!

  • @samuelmartin7319
    @samuelmartin7319 Před 3 lety +33

    Honestly your videos are the best! Keep posting!

  • @md2perpe
    @md2perpe Před 2 lety +27

    One reason that incomplete data types are allowed is that they are sometimes necessary. You might have two structs that reference each other with pointers, and then you must first put an incomplete declaration of one of the structs, then a complete declaration of the other, and lastly the complete declaration of the first one.

  • @LDdrums20
    @LDdrums20 Před 2 lety +8

    To me, this is the best pattern I learned for C. This makes everything better!

  • @dallas_barr
    @dallas_barr Před rokem

    Very well explained ! First time on this channel and instant subsciption !

  • @pathayes2224
    @pathayes2224 Před 3 lety +2

    Excellent explanation. They key was the forward declaration of the struct

  • @duyanhpham6823
    @duyanhpham6823 Před 3 lety

    Thank you, sir! This is exactly what I'm waiting for!

  • @267praveen
    @267praveen Před 2 lety

    Heard a lot about incomplete data types but your video explains perfectly. Thanks.

  • @ashishm8850
    @ashishm8850 Před 2 lety

    This was very helpful. Thanks!

  • @fontanot
    @fontanot Před 3 lety

    thanks for this,, it is awesome how you conveyed this information

  • @Neeseius
    @Neeseius Před 3 lety +6

    Very nice video, thank you.
    I'd like to see a video on virtual function tables on structs. I am a Go programmer and it's convenient having function members that tell me exactly what my "object/struct-instance" can do simply by typing a period. In Go, struct methods are just syntactic sugar for "mystruct.mymethod(&mystruct, somevar);". However in Go, "&mystruct" is passed implicitly into the function if it is a pointer receiver. Go doesn't have inheritance either, and IMO it's very similar to C. I am new to C but after watching a few of your videos I wrote a simple Vector struct but I wanted to emulate go so it worked like:
    Vector* my_vector = new_vector(100);
    my_vector->add(my_vector, 1);
    etc...

  • @nate_d376
    @nate_d376 Před 3 lety

    Very good information! Thanks.

  • @kuriel07
    @kuriel07 Před rokem +3

    hi, instead of using 2 malloc (one for meta data and data), i'd suggest using array inside the allocated data
    typedef struct {
    int size;
    int num_entries;
    int head;
    int tail;
    int values[0];
    } queue;
    you can allocate them using
    queue * q = malloc(sizeof(queue) + max_size);
    the structure can be free using single free, just suggestion, i usually use this myself

    • @Ryan-in3ot
      @Ryan-in3ot Před 10 měsíci

      this is clever and does work well but if you intend to use realloc (or a similar custom function) to resize the array then you have to be able to reassign the value of the original pointer. This can potentially create problems that are avoided by just having a pointer to separately alloc'd memory in the struct, since in that case you can realloc that pointer and the leave the struct pointer untouched.

  • @austecon6818
    @austecon6818 Před 2 lety +1

    This is great. Who needs C++?

  • @otmanm4095
    @otmanm4095 Před 3 lety +1

    Thank you very much for teaching C to us!

  • @mockingbird3809
    @mockingbird3809 Před 3 lety +12

    Can you make a video on polymorphism and casting a pointer of one struct type into another like we do `struct sockaddr_in` to `struct sockaddr` and other places and how pointer casting between structs can give us polymorphism behavior in C? It will be really helpful if you can make a video on this specific topic. Anyways, amazing video as always :-)

    • @JacobSorber
      @JacobSorber  Před 3 lety +5

      Thanks. Good topic idea. I'll see what I can do.

    • @sourestcake
      @sourestcake Před 2 lety +1

      Keep in mind that this is a violation of strict aliasing, unless you have all those structs in an union. If you put sockaddr_in and sockaddr into an union, you can alias both with the union type wihout violating strict aliasing. You just can't alias plain sockaddr_in and sockaddr with each other. You can include sockaddr_storage in the union, and it should have enough space for any of the sockaddr types.

  • @tiagomelojuca7851
    @tiagomelojuca7851 Před 2 lety

    I love a channel. Thanks Jacob, u are the man.

    • @JacobSorber
      @JacobSorber  Před 2 lety

      You're welcome. Glad you're enjoying it.

  • @TutaMuniz
    @TutaMuniz Před 3 lety

    Wow! Great video!

  • @francoismartineau2519

    Awesome!

  • @rtzstvn
    @rtzstvn Před 3 lety

    great video, thanks!

  • @markuscwatson
    @markuscwatson Před rokem

    Love it!

  • @LDdrums20
    @LDdrums20 Před 2 lety

    And there's a step more on top of this. Using a handle or a void * to the struct so you don't even expose the struct type. Really handy for libraries.

  • @botbeamer
    @botbeamer Před 3 lety +1

    Amazing, I already know all of this but glad to see that you're making these high quality beginner videos.

  • @nexusclarum8000
    @nexusclarum8000 Před 2 lety

    I've heard a few compelling arguments about not abusing typedefs and polluting the global namespace. I prefer to typedef only in the .c where it can help to clean up some of the verbosity of the implementation. But in the .h keep the "interface" clear on exactly what is what.

  • @dirtymint
    @dirtymint Před 3 lety +2

    Is the typedef queue* technique similar to a forward declaration in C++?

  • @EmbeddedGorilla
    @EmbeddedGorilla Před 2 lety +2

    Thank you for the video, I was implementing all my libraries but without the last step (an important one :P). However I have a question.
    I don't really understand the difference between:
    void queue_init (queue_t* queue, int max_size) vs queue_t* queue_init (int max_size). Could someone explain me why one is better that another

  • @ameynarkhede8264
    @ameynarkhede8264 Před 3 lety +2

    Can you do video on Implementing simple custom malloc? Great video btw thanks!

    • @mohammedjawahri5726
      @mohammedjawahri5726 Před 3 lety +2

      try randal bryant's and o'hallaron's computer systems book, there is an excellent section on memory allocators (and an exercise that has you writing a _really_ solid one from scratch)

  • @imad9948
    @imad9948 Před 3 lety

    Best channel ever

  • @Uerdue
    @Uerdue Před 2 lety

    Is there any benefit in repeating the typedef in the source file?
    Just declaring the `struct myq { int *values; /*...*/ };` and including the header (which has the typedef) should still compile, right?

  • @pladimir_vutin
    @pladimir_vutin Před rokem

    niceee!

  • @nordgaren2358
    @nordgaren2358 Před 2 lety

    Is it possible to baboozle the linker by guessing the signature for something that isnt in the .h of a library? I know that the names of functions aren't in the compiled code, but I have been wondering about this.
    I'm learning in the land of game modding, and we call the games code all the time, but I think usually you want to find a pointer to that function, but can you theoretically call a function just by guessing the signature?
    Ive used Microsoft detours to intercept a games call to the windows API (Dark Soul Remastered changes your PC mouse settings and I made a mod to stop it 😂) and that seems to just use the function signature.
    Any thoughts? Thanks for the great videos!

  • @ChrisBNisbet
    @ChrisBNisbet Před 2 lety +1

    I'd like to see a video about benefits of making header files standalone-compilable, so they contain all other headers required to ensure the header can be compiled on its own, and why this is a good thing (or downsides, if there are any).

  • @sababugs1125
    @sababugs1125 Před 3 lety

    Is it possible to make parts of the struct accessable and others not ?

  • @neodaltiair8624
    @neodaltiair8624 Před 2 lety

    Is the burden of defining the struct on the users of the header interface then? Or if it’s defined in the my_queue file how can a user properly create a queue struct and assign all/correct attributes?

  • @mockingbird3809
    @mockingbird3809 Před 3 lety +2

    Do you do code review, like reviewing my C library and such? If so, how can I approach you?

    • @JacobSorber
      @JacobSorber  Před 3 lety

      I do, occasionally. More instructions here: czcams.com/video/k2K2HVg4Arc/video.html

  • @technics6215
    @technics6215 Před 2 lety

    Sooo if you can't see the size of it - you can't malloc them?

  • @antonioastorino7488
    @antonioastorino7488 Před rokem

    Well explained. However, there was a good opportunity for mentioning "getters" and "setters" which I would have taken :)

  • @00zuu00
    @00zuu00 Před 3 lety

    Right in time! I just started a project that needed opaque pointers. I'm wondering if it is possible to declare the struct in the main file, not just the pointer and then pass the address (&struct) to the functions. I'm working with embedded devices and malloc is not supported. Thanks!

    • @wumi2419
      @wumi2419 Před 3 lety

      I don't know if i'm understanding question right, but i'll try to answer.
      You will still need to either allocate memory somehow or make global variable for structure, because if it's created in stack of some function, it will disappear once that function ends. You probably can do some crazy workaround like keeping function that creates objects in separate tread, which is mostly sleeping, so function won't end and free the stack, and treat that as a "malloc", but it is way too much overhead.

    • @wumi2419
      @wumi2419 Před 3 lety

      Got another idea, that is probably closer to what you need.
      You can create another struct, of same size. Creation function will return this "faux" struct as value, and all functions that need to work with your struct will take pointers to faux struct. Since sizes are same, you will need to convert the pointers, but other than that, everything should be fine.
      This allows you to store struct without malloc in a way that allows it to persist. So for example you have vector type, of size 42 (for some unknown reason), it will look somewhat like this
      header:
      typedef struct {
      char[42];
      } fauxVec;
      float norm(fauxVec* vec);
      code:
      typedef struct {
      } vec_t
      float norm(fauxVec* vec) {
      vec_t* trueVec=(vec_t*)vec;
      }

    • @00zuu00
      @00zuu00 Před 3 lety

      @@wumi2419 Hi, thanks for answering. Maybe I wasn't clear enough. Following the queue example, what if I want the queue instances to be declared at compilation time. I don't need a function that allocates the struct and returns the pointer. The user should be able to create as many instances as wanted when using the API but again everything is allocated at compilation time, nothing dynamic. Something like:
      #include "queue.h"
      Queue q1;
      Queue q2;
      int main() {
      foo(&q1, args);
      }

    • @deepakr8261
      @deepakr8261 Před 3 lety

      @@00zuu00 Nope this wont be possible as you cannot create an instance for an opaque type in main. If your concern is regarding usage of dynamic allocation, you can use a large object pool of required structs at compile time and when queue_create is requested, pass on the index from the object pool as the address of the queue.

    • @00zuu00
      @00zuu00 Před 3 lety

      @@deepakr8261 Ok, thanks! It makes sense if I think of it now, just wanted to know if there was a way to do it that I was missing. The object pool idea is not bad, nevertheless my objects are "big" compared to the available memory, I'll investigate further for alternatives.

  • @samuelmartin7319
    @samuelmartin7319 Před 3 lety +1

    I have a couple of questions:
    1. Do opaque types have to be a pointer/address of that struct?
    2. Is there anyone to declare “public” variables in the .h but still have other “private” variables declared in the .c file?

    • @wilfreddevries294
      @wilfreddevries294 Před 3 lety +1

      1. At compile time, the compiler cannot tell the size of the struct. This is why it cannot create memory on the heap, and instead we need to `malloc` the appropriate amount of memory.
      2. Yes, any variable that isn't exposed in the header file, is limited to the scope of the translation unit it's declared in (aka the C file).
      I think that that is default behaviour, and you can use `static` to make it explicit, but I am not sure about that.

    • @wilfreddevries294
      @wilfreddevries294 Před 2 lety

      @@anon8510 I think Samuel changed his question.

  • @clemenskrenn3930
    @clemenskrenn3930 Před 2 lety +1

    is there a way of doing this if the struct is not passed as a pointer to a function declared in the .h file?

    • @spudgossie
      @spudgossie Před rokem +2

      I'd say nope. A pointer to 'anything' is the same size 4 bytes for x86 8 bytes for x64. This is the power and the pain of C type languages. As long as you know what your doing the compiler will go along with you but... you'd better be right

    • @clemenskrenn3930
      @clemenskrenn3930 Před rokem

      @@spudgossie I agree - thank you for your answer!

  • @edwinontiveros8701
    @edwinontiveros8701 Před 2 lety

    I thought the concept of 'opaque types' in C referred to void * pointers, typedefed to a meaningful name such as 'queuehandle_t', and then typecasted to (or assigned to a pointer variable of) the correct type inside of the function. I guess this is more typesafe and i need to do some refactoring now :P.

  • @lordadamson
    @lordadamson Před 3 lety +9

    Awesome video!
    Often times C would lack features and one would have to either live with the fact that these features don't exist or make crazy workarounds that are questionable and often insane.
    However, in the case of abstraction, I think C does it the best. I code C++ but I still make my module interfaces the C way. C really does hide all the things that don't need to be shown, while pretty much every other language would show these things but with a special keyword like 'private' which introduces dependency issues. So now the C++ people do workarounds to imitate the C way of doing things, and do things like the 'pimpl' idiom.
    In short, C interfaces win over pretty much every other language.

    • @sghsghdk
      @sghsghdk Před 3 lety +1

      I totally agree. Encapsulation in C++ is broken by design.

    •  Před 2 lety +1

      @@sghsghdk I see a problem with the need of declaring all the class in the same file. I think it would be better if the header had the public part and the cpp, the private part. The protected maybe it would be better placed in the header... but I don't know enough about C/C++.

  • @nazarottto
    @nazarottto Před 3 lety +2

    What if I cannot use malloc()? In embedded software development, especially when it comes to bare metal, dynamic memory application is a no-go and so I have no choice but to expose my implementation in the header file.

    • @lordadamson
      @lordadamson Před 3 lety +1

      Yes, at which point the easiest way to do it is just exposing the struct but have underscores prefixed to every member variable. By convention people don't mess with member variables that begin with underscore.

    • @coelhinhofofo6299
      @coelhinhofofo6299 Před 3 lety +1

      Hey! Would you mind telling me more about why we shouldn't use dynamic memory allocation in embedded systems?

    • @JacobSorber
      @JacobSorber  Před 3 lety +3

      Good idea for a future video. Thanks. Short answer is, it introduces nondeterminism, which can introduce bugs, especially when you don't have much memory to work with.

    • @JacobSorber
      @JacobSorber  Před 3 lety +4

      Also, one option is to use an object pool - acts like dynamic memory allocation, but with much less nondeterminism.

    • @nazarottto
      @nazarottto Před 3 lety +2

      @@lordadamson I personally prefer subtle comments, like // DO NOT MESS WITH THE INTERNALS, USE THE DAMN API OR I WILL DISABLE DARK MODE ON YOUR IDE!!@#$%& xD

  • @zxuiji
    @zxuiji Před 2 lety

    you don't need the second typedef, just the struct definition

  • @danielsantrikaphundo4517

    12:15 I was, yes :)

  • @breathermachine2
    @breathermachine2 Před 2 lety

    Looks like there's a bug in your queue or in your test. The test queues 6 values but only prints the first 5.

  • @alkaratus9189
    @alkaratus9189 Před rokem

    I undestand how to use this shema for encapsulation... But i still cant understand how to use it for polymorphysm or inheritence, unless it is not a case of Opaque Types

    • @JacksonBockus
      @JacksonBockus Před měsícem

      You can use composition to do inheritance with this. If you have struct foo, you can have another struct bar whose first member is a struct foo. Now you can cast a pointer to a bar to a pointer to a foo and it will work fine because both structs begin at the same time.
      Polymorphism is a little more complicated, but there’s a way to do it with function pointers as members of the parent class.

  • @adammontgomery7980
    @adammontgomery7980 Před 3 lety

    I understand that an interface where implementation details don't need to be obvious to the user is important. I also know that public member access is "wrong" in most cases, but I don't know why. Could anyone explain why?

    • @srenh-p3798
      @srenh-p3798 Před 3 lety

      In this case it's because the attributes in the queue struct are used to represent the queue's state. If you fiddle with these directly, e.g. you decrease the "head" variable directly, then the next time you try to enqueue an element you would end up overwriting data.

  • @Rafa-tt8ee
    @Rafa-tt8ee Před rokem

    The top g strikes again.

  • @EnkoVlog
    @EnkoVlog Před 3 lety

    Another way how to hide implementation just provide *.h file and library *.so instead of *.c file

    • @dealloc
      @dealloc Před 3 lety +1

      That's not so much "hiding" implementation but just making it consumable by dynamically or statically link to a binary which contains the implementation and the header file as a reference for what is exposed. The concept that this video talks about is about API surface area to prevent unintentional behavior from changing internal state.
      The example in the video, where he was able to change head field would not perform the necessary steps to ensure the queue works as intended.

  • @neillunavat
    @neillunavat Před 3 lety

    Please remove the bottom right red icon... It actually hinders with the overall theme of the video.
    I really like your videos. Which university do you teach in? I would love to get in and have you as my teacher :)

    • @JacobSorber
      @JacobSorber  Před 3 lety

      I teach at Clemson University. See you in class. 😀

    • @neillunavat
      @neillunavat Před 3 lety +1

      @@JacobSorber Already researching about it 😃
      But technically I am still 15.. Maybe I should focus on High School for now. 😅

  • @maurycykujawski6344
    @maurycykujawski6344 Před 2 lety

    I think your definition of abstraction is not entirely correct.
    I can make abstraction that doesnt hide anything from user. If you define abstraction as simply "naming something" then it makes more sense even outside of CS. I think it may be beneficial to think of abstracting data as naming it. If I make a struct holding int x and int y and name it Point, ive added more abstraction to the code.

  • @wisentini
    @wisentini Před 3 lety

    First!

  • @JusticeNDOU
    @JusticeNDOU Před 2 lety

    dude just use python already

    • @JusticeNDOU
      @JusticeNDOU Před 2 lety

      i suffered for years with strict datatypes until there was python and i could not believe what the gang of two did ... 10 years now i have never touched c again in my life

    • @nonnullptrhuman504
      @nonnullptrhuman504 Před 2 lety

      @@JusticeNDOU But what if i want performance instead of easy to type, slowness of python?

    • @JusticeNDOU
      @JusticeNDOU Před 2 lety

      @@nonnullptrhuman504 depends on what you are coding, what are you coding ?

    • @nonnullptrhuman504
      @nonnullptrhuman504 Před 2 lety

      @@JusticeNDOU kernel development, opengl

    • @Hellohiq10
      @Hellohiq10 Před 2 lety

      I used python and got overwhelmed by the standard library

  • @thedoublehelix5661
    @thedoublehelix5661 Před 3 lety

    Why not have queue in the header file be void? That way the pointer would be a void pointer.

    • @JacobSorber
      @JacobSorber  Před 3 lety

      You definitely could do that. I'm not sure what the advantages would be, though. It would make the type a little more opaque.

    • @thedoublehelix5661
      @thedoublehelix5661 Před 3 lety

      @@JacobSorber Oh I do that just so I don't have to name my struct before I typedef it. Other than that I don't think there are any real advantages

    • @gergelycsontos1435
      @gergelycsontos1435 Před 3 lety

      My concern with using void* as the argument is that any pointer is implicitly casted to void* . So if you accidentally pass a different pointer to the function it will compile without warning. I had to work in an environment like that, and I used "magic id" as a first field to be sure I have the correct struct, but that is a runtime check. I prefer to have a compile time warning/error.