Sign in to follow this  
Trillian

Understanding C-style programming

Recommended Posts

Hello! I wish to understand something that I find mysterious about C-style programming. I've seen many libraries that create function like the following :
void SelectBuffer(char * buf, int size);
void WriteToBuffer(char mychar);
void UnselectBuffer();
The thing that remains mysterious to me is how the library stores its buffer pointer and buffer size... It doesn't seem to be using a class or anything, how can the data be shared between these multiple functions?

Share this post


Link to post
Share on other sites
well you could just setup all the data (struct, classes, int, floats, etc) in int main () and pass the data through the functions that way. how reasonable or optimized that is, is another question.

Share this post


Link to post
Share on other sites
OpenGL has a context that is shared by everyone (usually either per thread, per app, or per window depending on platform / implementation).

Realize that in the orignial VGA card invention days, the video display was just a magic address, and there was no such thing as calling a function to get it, or passing it as a pointer, because it was simply a truth ... write to address 0x00170000 (just an example) to change the screen contents. In fact prior to APIs like windows, most machine facilities we're accessed this way. Port ??? yields the console, Port ??? the serial output 1, etc. A large amount of what was contained in the technical documentation for a machine / platform was a collection of just this sort of hard-coded register / address / flag information. Basically anything you can imagine being wrapped with a singleton, or static class member, would just as easily be accessed globally or statically in pure C code. It was not normal in those early (C style) days to pass around addresses / values which could never change, because people we're trying make a particular computer do a particular thing. During the years of doing things this way, people invented strategies that work better for cases where machines or needs change over time, or new features force changes on old systems.

Share this post


Link to post
Share on other sites
Quote:
Original post by Trillian
I wish to understand something that I find mysterious about C-style programming. I've seen many libraries that create function like the following :
void SelectBuffer(char * buf, int size);
void WriteToBuffer(char mychar);
void UnselectBuffer();

The thing that remains mysterious to me is how the library stores its buffer pointer and buffer size... It doesn't seem to be using a class or anything, how can the data be shared between these multiple functions?


Here's my guess as some C-styled pseudo code based on those prototypes.


#include <malloc.h>

static char * g_buffer;
static int g_buffer_size;

void SelectBuffer(char * buf, int size)
{
g_buffer_size = size;
g_buffer = buf;
}

void WriteToBuffer(char mychar)
{
write(g_buffer, mychar); // where write transfers mychar to the buffer
}

void UnselectBuffer(void)
{
g_buffer = NULL;
g_buffer_size = 0;
}

int main(int argc, char **argv)
{
size_t buffersize = 0x10000; // 64 kb
char *buffer = malloc(buffersize); // acquire the buffer

SelectBuffer(buffer, buffersize);

WriteToBuffer('A');

UnselectBuffer();

free(buffer); // release the buffer

return 0;
}


Share this post


Link to post
Share on other sites
Before you go off and write all your C code that way let me add that it's quite possible to put in a bit more 'OOP' flavour into C.
Instead of storing the raw pointer and size variables independently, you could merge them into a struct, like so:


typedef struct {
char* data;
int size;
} buffer_t;

void SelectBuffer(buffer_t* buffer);
void WriteToBuffer(buffer_t* buffer, char mychar);
void UnselectBuffer(buffer_t* buffer);


Internally, the library could manage the buffer objects by storing them on the heap (in a list or array or whatever), by which the amount of global variables would be reduced a lot:


// in buffer.c:
struct buffer_t** buffer_list;


Then your functions (and the users of the functions) can operate more cleanly on buffer objects, and even deal with multiple buffer objects. Hopefully you agree that this way is a bit better than the API in your initial post. Chances are that in the future you might want to add new things to the buffer, which in the case of using the buffer struct becomes a lot less work to do that and doesn't break any API functions.

Share this post


Link to post
Share on other sites
Quote:
Original post by basement
...


That is the exact approach that I took for my Win32 input library that I am currently working on. (I actually based my design on reading this thread) In doing so, it is more flexible in terms of functionality as well as changes, all of which you pointed out.

If I would have taken the general approach (I've seen it refered to as a "only one instances") then the user would not be able to use my library in multiple windows or event have different configurations. The encapsulation provided makes it really easy to maintain and modify the project.

I even took it one step further and used the Pimpl pattern so I can change what I needed during development (usually add more stuff) and the main struct that is used does not change at all and the API is the same. In addition, the user does not know and cannot mess with the internal workings, which goes into a procedural based information hiding technique.

Of course, there are the drawbacks of what seems to be a 'better' design. First you will always have to validate all your pointers to the structs you are using. You'd never want to pass by value with something that is called a lot and is of a decent size, since it'd copy everything each time, so you will definitly have to work with pointers. If the user is just going to be using one single 'instance', then it might be an inconvience to the user to have to always pass the same pointer over and over.

In that regard, I will be thinking about making a second API though a defined value that can be used in the input library so if the user only needs one instance, then they can use an API similar to what Trillian has in the original post. If the user needs the extra functionality though multiple instances, then it will be avaliable as well. The library will still be designed around what basement posted though.

For your original post, it'd might look like:


typedef struct
{
char* data;
int size;
} buffer_t;

void SelectBuffer(buffer_t* buffer);
void WriteToBuffer(buffer_t* buffer, char mychar);
void UnselectBuffer(buffer_t* buffer);

#ifdef USE_ONLY_ONE
buffer_t g_Buffer;
#define SelectBuffer() SelectBuffer(g_Buffer)
#define WriteToBuffer(x) SelectBuffer(g_Buffer, x)
#define UnselectBuffer() UnselectBuffer(g_Buffer)
#endif


But that's just a random idea, not sure if it's a good idea to actually do [wink]

Share this post


Link to post
Share on other sites
Guest Anonymous Poster
Quote:
Original post by basement
Before you go off and write all your C code that way let me add that it's quite possible to put in a bit more 'OOP' flavour into C.
Instead of storing the raw pointer and size variables independently, you could merge them into a struct, like so:


typedef struct {
char* data;
int size;
} buffer_t;

void SelectBuffer(buffer_t* buffer);
void WriteToBuffer(buffer_t* buffer, char mychar);
void UnselectBuffer(buffer_t* buffer);


Internally, the library could manage the buffer objects by storing them on the heap (in a list or array or whatever), by which the amount of global variables would be reduced a lot:


// in buffer.c:
struct buffer_t** buffer_list;


Then your functions (and the users of the functions) can operate more cleanly on buffer objects, and even deal with multiple buffer objects. Hopefully you agree that this way is a bit better than the API in your initial post. Chances are that in the future you might want to add new things to the buffer, which in the case of using the buffer struct becomes a lot less work to do that and doesn't break any API functions.

agreed!

Share this post


Link to post
Share on other sites
Just for the record, I wasn't recommending using hidden (global / cnotext) variables, I was just explaining to the OP that they exist, and they used to the THE way of dealing with cases which these days are dealth with more explicitly.

Of course this is at a time when pushing 4 unneccesary bytes onto the stack was considered inefficient too.

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this