Coding Standards for pure C (not C++)

2019-01-30 19:48发布

I come from a java background (from my CS classes) and a semester of C++. I am just finishing up a OpenCV project for my Co-Op that's in pure C, so I'm a bit late in asking this question.

What are the design processes and coding standards for pure C?

I'm familiar with object oriented programming, design and best practices. I'm just a bit at a loss at a non-object oriented language like C. Every single variable and function appears to be global. It makes it feel like a real mess to me.

9条回答
The star\"
2楼-- · 2019-01-30 20:18

I honestly don't think that any number of answers on StackOverflow are going to teach you how to design and write well-structured C programs. You need to read a good book, and the obvious one to read is The C Programming Language by Kernighan & Ritchie.

查看更多
别忘想泡老子
3楼-- · 2019-01-30 20:18

You can restrict visibility of file-scope variables and functions to their respective source files (although that doesn't prevent you from passing pointers to these objects around).

For example:

/** foo.c */
static void foo_helper() {...} /* foo_helper cannot be called by name 
                                  outside of foo.c */
static int local_state;        /* local state is visible at file scope,
                                  but is not exported to the linker */
查看更多
Root(大扎)
4楼-- · 2019-01-30 20:19

I have no professionnal experience on C (only on C++), so don't take my advices, tricks and tips too seriously, as they are "object-like-oriented".

Almost Object C?

Simulating basic object-like features can be done easily:

In the header, forward declare your type, typedef it, and declare the "methods". For example:

/* MyString.h */

#include <string.h>

/* Forward declaration */
struct StructMyString ;

/* Typedef of forward-declaration (note: Not possible in C++) */
typedef struct StructMyString MyString ;

MyString *       MyString_new() ;
MyString *       MyString_create(const char * p_pString) ;
void             MyString_delete(MyString * p_pThis) ;
size_t           MyString_length(const MyString * p_pThis) ;

MyString *       MyString_copy(MyString * p_pThis, const MyString * p_pSource) ;
MyString *       MyString_concat(MyString * p_pThis, const MyString * p_pSource) ;

const char *     MyString_get_c_string(const MyString * p_pThis) ;
MyString *       MyString_copy_c_string(MyString * p_pThis, const char * p_pSource) ;
MyString *       MyString_concat_c_string(MyString * p_pThis, const char * p_pSource) ;

You'll see each functions is prefixed. I choose the name of the "struct" to make sure there won't be collision with another code.

You'll see, too, that I used "p_pThis" to keep with the OO-like idea.

In the source file, define your type, and define the functions:

/* MyString.c */

#include "MyString.h"

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

struct StructMyString
{
   char *      m_pString ;
   size_t      m_iSize ;
} ;

MyString * MyString_new()
{
   MyString * pMyString = malloc(sizeof(MyString)) ;

   pMyString->m_iSize = 0 ;
   pMyString->m_pString = malloc((pMyString->m_iSize + 1) * sizeof(char)) ;
   pMyString->m_pString[0] = 0 ;

   return pMyString ;
}

/* etc. */

If you want "private" functions (or private global variables), declare them static in the C source. This way, they won't be visible outside:

static void doSomethingPrivate()
{
   /* etc. */
}

static int g_iMyPrivateCounter = 0 ;

If you want inheritance, then you're almost screwed. If you believed everything in C was global, including variable, then you should get more experience in C before even trying to think how inheritance could be simulated.

Misc. Tips

Avoid multiple code-paths.

For example, multiple returns is risky. For example:

void doSomething(int i)
{
   void * p = malloc(25) ;

   if(i > 0)
   {
      /* this will leak memory ! */
      return ;
   }

   free(p) ;
}

Avoid non-const globals

This include "static" variables (which are not static functions).

Global non-const variables are almost always a bad idea (i.e. see C API strtok for an example of crappy function), and if producing multithread safe code, they are a pain to handle.

Avoid name collision

Choose a "namespace" for your functions, and for your defines. This could be:

#define GROOVY_LIB_x_MY_CONST_INT 42

void GroovyLib_dosomething() ;

Beware defines

Defines can't be avoided in C, but they can have side effects!

#define MAX(a, b) (a > b) ? (a) : (b)

void doSomething()
{
   int i = 0, j = 1, k ;
   k = MAX(i, j) ;   /* now, k == 1, i == 0 and j == 1 */
   k = MAX(i, j++) ; /* now, k == 2, i == 0 and j == 3, NOT 2, and NOT 1 !!! */
}

Initialize your variables

Avoid declaring variables without initializing them:

int i = 42 ; /* now i = 42 */
int j ;      /* now j can have any value */
double k ;   /* now f can have any value, including invalid ones ! */

Uninitialized variables are causes of painful bugs.

Know all the C API

The C API function list as described in the K&R is quite small. You'll read the whole list in 20 minutes. You must know those functions.

Wanna some experience?

Rewrite the C API. For example, try to write your own version of the string.h functions, to see how it is done.

查看更多
做自己的国王
5楼-- · 2019-01-30 20:21

Work with other good C programmers. Have a code review with them. Not only let them look at your code, but you look at their code.

查看更多
欢心
6楼-- · 2019-01-30 20:29

You may want to have a good look at the source of The Linux Kernel..... BTW it is not the easiest piece of code to start with but since you are from a programming background, it may help... As an object-oriented programmer, you might particularly find error-handling in C an uphill task. May be this helps---> http://www.freetype.org/david/reliable-c.html

查看更多
趁早两清
7楼-- · 2019-01-30 20:37

You can do object oriented design in pure C. An easy approach is to think of a module as a class with public methods as ordinary functions that require the this parameter as an explicit first argument.

It helps if the class name is a prefix on the function name, and if all private functions and class data are declared static. You build constructors with malloc() to get the memory, and explicit initialization of the data fields.

A constructor for an object with entirely private data members can expose an opaque pointer (typed void * even, or as a pointer to an incomplete type if type safety is desired). If you want to have only public data members, then a pointer to a publicly defined struct works well.

This pattern is followed by a number of libraries. An initialization function returns a cookie that must be passed back to all library methods, and one method serves as a destructor.

Of course, there are other ways to design well in pure C, but if OO works for you, you don't have to abandon it completely.

查看更多
登录 后发表回答