纯C(非C ++)的编码标准

时间:2009-08-11 19:39:17

标签: c coding-style

我来自java背景(来自我的CS课程)和一个学期的C ++。我刚刚为我的Co-Op完成了一个纯C的OpenCV项目,所以我问这个问题有点晚了。

纯C的设计流程和编码标准是什么?

我熟悉面向对象的编程,设计和最佳实践。我只是在像C这样的非面向对象语言上有点不知所措。每个变量和函数看起来都是全局的。这让我感到非常混乱。

11 个答案:

答案 0 :(得分:26)

您可能有兴趣查看我不久前问过的similar question的答案。此外,如果您对C风格指南感兴趣,您可能需要查看this page,因为它是C(和C ++)样式指南的存储库。如果您想要好笑,请查看NASA C Style Guide。特别是,看看大量的评论......你会知道我在谈论哪一个。请不要写这样的评论。

我个人推荐Indian Hill C Style Guide进行一些修改。此外,如果您在使用C语言设计大型程序时遇到问题,则可能需要购买该书C Interfaces and Implementations

答案 1 :(得分:15)

老实说,我认为StackOverflow上没有任何答案 将教你如何设计和编写结构良好的C程序。你需要阅读一本好书,而Kernighan& amp;和你能看到的明显的一本书是The C Programming Language。里奇。

答案 2 :(得分:10)

我对C没有专业经验(仅限C ++),所以不要过于认真地对待我的建议,技巧和提示,因为它们是“面向对象的”。

几乎是对象C?

可以轻松地模拟基本类似对象的功能:

在标题中,forward声明你的类型,typedef它,并声明“方法”。例如:

/* 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) ;

您会看到每个功能都有前缀。我选择“struct”的名称以确保不会与其他代码发生冲突。

你也会看到我用“p_pThis”来保持类似OO的想法。

在源文件中,定义您的类型,并定义函数:

/* 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. */

如果您想要“私有”函数(或私有全局变量),请在C源中将它们声明为静态。这样,它们就不会在外面看到:

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

static int g_iMyPrivateCounter = 0 ;

如果你想要继承,那么你几乎搞砸了。如果您认为C中的所有内容都是全局的,包括变量,那么在尝试考虑如何模拟继承之前,您应该获得更多C语言经验。

混杂。提示

避免使用多个代码路径。

例如,多次退货是有风险的。例如:

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

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

   free(p) ;
}

避免使用非const全局

这包括“静态”变量(不是静态函数)。

全局非const变量几乎总是一个坏主意(例如,参见C API strtok作为一个蹩脚函数的例子),如果生成多线程安全代码,它们很难处理。

避免名称冲突

为您的函数和定义选择“命名空间”。这可能是:

#define GROOVY_LIB_x_MY_CONST_INT 42

void GroovyLib_dosomething() ;

注意定义

在C中无法避免定义,但它们可能有副作用!

#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 !!! */
}

初始化变量

避免在不初始化变量的情况下声明变量:

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 ! */

未初始化的变量是导致痛苦的错误的原因。

了解所有C API

K&amp; R中描述的C API函数列表非常小。你会在20分钟内阅读整个清单。你必须知道这些功能。

想要一些经验吗?

重写C API。例如,尝试编写自己的string.h函数版本,看看它是如何完成的。

答案 3 :(得分:8)

通过将其声明为“静态”,可以使函数或对象的作用域位于其源文件的本地。这有点帮助。

否则,我看到的避免命名空间冲突的典型习惯是在名称上放置某种设施标识符。例如,foo.c中的所有内容都可能被命名为foo _ *

答案 4 :(得分:3)

与其他优秀的C程序员合作。与他们进行代码审核。不仅让他们看看你的代码,还要看他们的代码。

答案 5 :(得分:3)

好消息是你可以用C语言进行半对象编程。你可以保护数据,暴露访问函数等。它可能没有C ++的所有功能,但是从我看到的其他人们的C ++代码,很多人无论如何都不习惯。换句话说,人们在一个类中编写C代码,在C中你可以编写没有类容器的相同代码。

首先,阅读一本关于C编程和风格的书,K&amp; R很好。其次,我建议您查看CERT Programming Standard。尽管该网站主要关注“安全”编码标准,但这里的大部分内容都是每个人都应遵循的通用代码质量标准。做这里提到的事情将提高你的质量,消除讨厌的错误,并作为副作用,使你的代码更安全。

答案 6 :(得分:2)

你可能想要很好地了解Linux内核的来源..... BTW它不是最简单的代码片段,但由于你来自编程背景,它可能会有所帮助......作为面向对象的程序员,您可能会特别发现C中的错误处理是一项艰巨的任务。可能这有帮助---&gt; http://www.freetype.org/david/reliable-c.html

答案 7 :(得分:1)

您可以在纯C中进行面向对象的设计。一种简单的方法是将模块视为class,将公共方法作为普通函数,将this参数作为显式的第一个参数。

如果class名称是函数名称的前缀,并且所有私有函数和类数据都声明为static,则会有所帮助。您使用malloc()构建构造函数以获取内存,并显式初始化数据字段。

具有完全私有数据成员的对象的构造函数可以公开不透明指针(类型为void *偶数,或者如果需要类型安全,则作为指向不完整类型的指针)。如果您只想拥有公共数据成员,那么指向公开定义的struct的指针效果很好。

此模式之后是许多库。初始化函数返回一个必须传递回所有库方法的cookie,一个方法用作析构函数。

当然,还有其他方法可以在纯C中很好地设计,但如果OO适合你,你就不必完全抛弃它。

答案 8 :(得分:1)

您可以将文件范围变量和函数的可见性限制为各自的源文件(尽管这不会阻止您将指针传递给这些对象)。

例如:

/** 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 */

答案 9 :(得分:0)

编码样式

systemd

systemd code quality tools

RIOT-OS

nginx

Git Source Guidelines

FFmpeg

curl

建筑

The Architecture of Open Source Applications那里的C程序包括Git,Nginx,Open MPI,GPSD(C和Python),GDB,FreeRTOS,Berkeley DB,Bash,Asterisk,

VLC (lots of architecture info)

源示例

以上任何项目都值得一读。也可以阅读标准命令行工具。 Linux内核也有很多设计文档。

答案 10 :(得分:0)

如果您对关键环境标准感兴趣,那么MISRA C可能对您很感兴趣。我发现它很有帮助。

您可以获得MISRA C 2004 here

更新后的MISRA C 2012 here的简要概述。