PDA

View Full Version : C99 (C language) Discussion



RadWolfie
June 5th, 2015, 01:05 AM
Hello everyone, I would like to discuss about C99 standard and how we code it.

I didn't realize I was coding in C++ 90% of the time when I was trying to do C language in the API support. So, the first thing I want to discuss first is how we do a "class" like in struct.



typedef struct {
void (*SetFunction1)(int, int);
} Class_Struct1;

void Class_Struct1_Set_Function1(int value1, int value2) {
//Do stuff here
}

void execFunction() {
int value1 = 1;
int value2 = 2;
Class_Struct1 cs1 = { Class_Struct1_Set_Function1 };
cs1.SetFunction1(value1, value2);
}


Now the question is... in what way do programmer standardize this struct class-like and the function names? Is it like...


//Struct function
void (*class_get_set_function)(...);
//"member" function
void class_get_set_function(...) {
}

or (This is what I think, except minor issue with certain unique function names.)


//Struct function
void (*get_set_function)(...);
//"member" function
void class_get_set_function(...) {
}

or...? I'm having difficulty to determine the standardize method for this. What I do know is they are lowercase plus each word has an underline in-between. I'll be graceful for your inputs.

P.S. I'm unable to use such as "get_data" in a struct's function pointer due to another name is already redefined in SQL header file.

Keywords for C99 support:

auto
break
case
char
const
continue
default
do
double
else
enum
extern
float
for
goto
if
inline
int
long
register
restrict
return
short
signed
sizeof
static
struct
switch
typedef
union
unsigned
void
volatile
while
_Bool
_Complex
Imaginary

sehe
June 5th, 2015, 08:45 AM
http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1256.pdf

Have fun.

RadWolfie
June 6th, 2015, 01:06 AM
My friend gave me a feedback about "method (http://en.wikipedia.org/wiki/Method_(computer_programming))" with initial letter m at the starting of the name. I gave it some thoughts about it and seems is acceptable to be used in a struct. So, to use in example would be:


typedef struct {
void (*m_set_function)(int, int);
} Class_Struct;


void class_set_function(int value1, int value2) {
//Do stuff here
}


void execFunction() {
int value1 = 1;
int value2 = 2;
Class_Struct cs1 = { class_set_function };
cs1.m_set_function(value1, value2);
}



The m_ part isn't needed inside the class section since there are different class names plus not shared in a header unless needs to be. If other oppose this, please input your opinion about this. Thanks.

JackalStomper
June 6th, 2015, 09:54 AM
Please don't use C OOP.
For the sake of an answer, heres how its commonly done.


#define _CRT_SECURE_NO_WARNINGS // msvc bullshit

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

typedef struct String
{
char* _buff;
const char* (*get)(String*);
} String;

const char* string_get(String* self)
{
return self->_buff;
}

String* string_new(const char* str)
{
String* out = (String*)malloc(sizeof(String));
int len = strlen(str);
out->_buff = (char*)calloc(len, sizeof(char));
strcpy(out->_buff, str);
out->get = &string_get;
return out;
}

void string_delete(String* self)
{
free(self->_buff);
free(self);
}

int main(void)
{
String* str = string_new("Hello world");
puts(str->get(str));
string_delete(str);
return 0;
}


There is no standard for C OOP. Because C is not an object oriented language.

What needs to be taken into account is that C "OOP" is not even OOP. Its struct oriented programming.

Lets break it down.

Some of the most common aspects of OOP:

Encapsulation
Composition/inheritance/delegation
Polymorphism
Open recursion


What does C have?

Encapsulation - No. All members and fields are public and mutable.
Composition/inheritance/delegation - Nope.
Polymorphism - Unless you count C's lack of type safety (void*) with pointers this is a no.
Open recursion - Somewhat, methods are passed an explicit "this" pointer rather than implicitly.


So why use C?

Some people claim performance. - Wrong. C OOP as its commonly written actually decreases performance!!

Why? take a look at the above code. Note how all "methods" are really just function pointers. This means for every function call during runtime the CPU must first look up the function address from a virtual method table. And THEN call it. This means useful CPU optimizations like branch prediction and caching are impossible.

And that's just runtime. A MUCH bigger impact is the compile time optimizations that are missed out by writing C like this. Just like the CPU, it's impossible for the compiler to know what function a pointer is going to call, so it can't perform any optimizations such as function inlining. This also means a lot of housekeeping instructions have to be generated for each method call.

This is akin to making a class in C++ and having every single method be virtual.

But these are all high performance problems and not very relevant to basically anything you're going to be making. And I don't mean gaming high performance, I mean billions of calls per second kind of performance.


What IS a big impact to you is the development time and code maintenance.
For example, there's a deliberate bug in the code I introduced up above. Can you find it?

The answer:

string_new is using strlen() on str, which gets the string length minus the null terminator
the code then allocates a block of memory using this length and copies the string to it.
But it copies with strcpy()

This means that the string is copied including the the null terminator.
The copied string is +1 length than what was allocated. The code is now in Undefined Behavior.
In this case, the code runs fine and prints "Hello world" without an issue.
But when you call string_delete(), the program will crash due to memory corruption.

How the code should have been written:


String* string_new(const char* str)
{
String* out = (String*)malloc(sizeof(String));
int len = strlen(str) + 1;
out->_buff = (char*)calloc(len, sizeof(char));
strncpy(out->_buff, str, len);
out->get = &string_get;
return out;
}




There is a valid argument for using C though. It's (psuedo) ABI. Almost every language on the planet has bindings for C.
But that can be utilized just as easily in C++ doing the following



extern "C" {
int some_c_abi_function(int);
}


There. The rest of your program could be written entirely in C++11 and your api is still bound in C! Simple!

Now lets take a look at the C++ equivalent of the above code


#define _CRT_SECURE_NO_WARNINGS // msvc bullshit

#include <cstring>
#include <cstdlib>
#include <iostream>

class String
{
char* _buff;
public:
String(const char* str)
{
int len = strlen(str) + 1;
try {
_buff = new char[len];
memcpy(_buff, str, len);
} catch (const std::exception&) {
_buff = nullptr;
}
}

~String()
{
if (_buff)
delete[] _buff;
}

const char* get()
{
return _buff;
}
};

int main()
{
String str("hello world");
std::cout << str.get() << '\n';
return 0;
}


Safer code, cleaner syntax, proper encapsulation, and no external memory management.


TLDR on C vs C++


You pick C when

you need portable assembler (which is what C is, really) for whatever reason,
your platform doesn't provide C++ (a C compiler is much easier to implement),
you need to interact with other languages that can only interact with C (usually the lowest common denominator on any platform) and your code consists of little more than the interface, not making it worth to lay a C interface over C++ code,
you hack in an Open Source project (many of which, for various reasons, stick to C),
you don't know C++.

In all other cases you should pick C++.

Btcc22
June 6th, 2015, 10:49 AM
This means useful CPU optimizations like branch prediction and caching are impossible.

To nitpick, this is true on certain architectures (PPC, where you'll take a huge performance hit for virtual calls) but indirect branch prediction has been around for quite a while. Developers would make efforts to avoid virtual calls on the Xbox 360 because it can matter for games.

Another nitpick would be trying to catch the std::bad_alloc exception. If you're out of memory, just let the program terminate. In many cases, the allocation is going to succeed and you'll end up hitting the wall when you actually attempt to make use of the memory, as the OS tries to back the page.

JackalStomper
June 6th, 2015, 12:37 PM
To nitpick, this is true on certain architectures (PPC, where you'll take a huge performance hit for virtual calls) but indirect branch prediction has been around for quite a while. Developers would make efforts to avoid virtual calls on the Xbox 360 because it can matter for games.

Another nitpick would be trying to catch the std::bad_alloc exception. If you're out of memory, just let the program terminate. In many cases, the allocation is going to succeed and you'll end up hitting the wall when you actually attempt to make use of the memory, as the OS tries to back the page.

Very true on both accounts! The try/catch for this particular instance is terrible, yes.
Guess its just a force of habbit from handling other things that throw in constructors. Unhandled exceptions in constructors can lead to very bad things.

RadWolfie
June 6th, 2015, 08:49 PM
There is no standard for C OOP. Because C is not an object oriented language.

What needs to be taken into account is that C "OOP" is not even OOP. Its struct oriented programming.


Yeah... I accidentally did that, so that's why I'm converting the interface class to struct containing function pointers in the public header files. This will reduce the exports and file size too. See far down below of my post for example I'm planning to do.



Some people claim performance. - Wrong. C OOP as its commonly written actually decreases performance!!

Why? take a look at the above code. Note how all "methods" are really just function pointers. This means for every function call during runtime the CPU must first look up the function address from a virtual method table. And THEN call it. This means useful CPU optimizations like branch prediction and caching are impossible.


Um... DirectX does this same thing as I had looked in its header file how it works to execute functions. You're saying DirectX's method also decrease performance?



TLDR on C vs C++

You pick C when

you need portable assembler (which is what C is, really) for whatever reason,
your platform doesn't provide C++ (a C compiler is much easier to implement),
you need to interact with other languages that can only interact with C (usually the lowest common denominator on any platform) and your code consists of little more than the interface, not making it worth to lay a C interface over C++ code,
you hack in an Open Source project (many of which, for various reasons, stick to C),
you don't know C++.
In all other cases you should pick C++.


It is needed to allow interaction with C API just like Windows did for their WINAPI. Internally still is in C++ as intended since I do need some of the C++ keywords to reduce the duplicates which serve same purpose.


To nitpick, this is true on certain architectures (PPC, where you'll take a huge performance hit for virtual calls) but indirect branch prediction has been around for quite a while. Developers would make efforts to avoid virtual calls on the Xbox 360 because it can matter for games.

I didn't know that, thanks for sharing with us. Luckily I'm coding hobby project(s) in Windows platform only. Plus I think Windows is not compatible with PPC architecture? Confirmed, PPC is not a x86 / x64 architecture. So that's one less thing to worry about.



Another nitpick would be trying to catch the std::bad_alloc exception. If you're out of memory, just let the program terminate. In many cases, the allocation is going to succeed and you'll end up hitting the wall when you actually attempt to make use of the memory, as the OS tries to back the page.
Unless you reserved a portion of memory to troubleshoot the cause? This might be helpful when using a very small reserved portion of memory to find out why it made an exception. Or perform an garbage collection to find what's need to keep and free up then try again to see if std::bad_alloc does not return.


Unhandled exceptions in constructors can lead to very bad things.
Hmm... I'll keep that noted whenever it happen.


Now, for a mock up of class in struct I'm intending to do is:



typedef struct IDemo {
int (*m_get_int(IDemo*);
void(*m_set_int(IDemo*, int);
} IDemo;

main() {
IDemo* pIDemo = init_demo();
pIDemo->m_set(pIDemo, 30);
int demo_int = pIDemo->m_get_int(pIDemo);
}

and


typedef struct IGlobal {
int (*m_get_int();
void(*m_set_int(int);
} IDemo;

main() {
IGlobal* pIGlobal = get_global();
pIGlobal->m_set(10);
int global_int = pIGlobal->m_get_int();
}


That way, I can use a member function (which really is a function pointer) to operate as C++ internally. And another is non-member function, which doesn't need to include itself as it is a single class.


Now it reminds me... in C# it doesn't allow an single variable to be used as getter/setter? aka must have 1 function each? To me, it seems take more work to do just for C#. P.S. Not an expert with C#, just well enough knowledge with C++ and most asm.