惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

F
Full Disclosure
WordPress大学
WordPress大学
小众软件
小众软件
Cloudbric
Cloudbric
AWS News Blog
AWS News Blog
腾讯CDC
量子位
人人都是产品经理
人人都是产品经理
大猫的无限游戏
大猫的无限游戏
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
V
Vulnerabilities – Threatpost
Scott Helme
Scott Helme
Hugging Face - Blog
Hugging Face - Blog
博客园_首页
C
CXSECURITY Database RSS Feed - CXSecurity.com
The Hacker News
The Hacker News
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
IT之家
IT之家
Jina AI
Jina AI
Attack and Defense Labs
Attack and Defense Labs
S
SegmentFault 最新的问题
Simon Willison's Weblog
Simon Willison's Weblog
The Cloudflare Blog
阮一峰的网络日志
阮一峰的网络日志
T
Tailwind CSS Blog
Last Week in AI
Last Week in AI
博客园 - 【当耐特】
Google Online Security Blog
Google Online Security Blog
美团技术团队
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
V
Visual Studio Blog
罗磊的独立博客
L
LINUX DO - 最新话题
博客园 - Franky
博客园 - 叶小钗
Apple Machine Learning Research
Apple Machine Learning Research
The Last Watchdog
The Last Watchdog
J
Java Code Geeks
AI
AI
C
Cisco Blogs
酷 壳 – CoolShell
酷 壳 – CoolShell
C
Cyber Attacks, Cyber Crime and Cyber Security
Cisco Talos Blog
Cisco Talos Blog
博客园 - 三生石上(FineUI控件)
雷峰网
雷峰网
Help Net Security
Help Net Security
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
云风的 BLOG
云风的 BLOG
I
Intezer
S
Securelist

Luca Cavallin

AI Engineering for Developers | Blog AI Engineering for Developers Platform Engineering End-to-End | Blog Google Cloud Networking 101: The Comprehensive TLDR | Blog Google Cloud Networking 101: The Comprehensive TLDR Containers Are Not Automatically Secure | Blog Containers Are Not Automatically Secure Watery Stone Beacon | Photography Blue Iceman Suture | Photography Hidden Emerald Pool | Photography Autumn Chapel Pinnacles | Photography A Tour of eBPF in the Linux Kernel: Observability, Security and Networking | Blog A Tour of eBPF in the Linux Kernel: Observability, Security and Networking Shared Violet Pulse | Photography Kubernetes Networking from Packets to Pods | Blog An Overview of Network Protocols | Blog An Overview of Network Protocols A Quick Journey Into the Linux Kernel | Blog A Quick Journey Into the Linux Kernel OpenTelemetry: A Guide to Observability with Go | Blog I'm on the Cillers Podcast Talking About Tech and Hackathons | Blog Yet Another List of Random Opinions on Writing Readable Code and Other Rants | Blog My post about Istio is now on the Istio blog too! | Blog Tropical Jungle Escape | Photography The Istio Service Mesh for People Who Have Stuff to Do | Blog Dreamy Cartoonscape Windmill | Photography Twilight Windmill Reflections | Photography Notes I took while reading "Applied Machine Learning and AI for Engineers" and "Introducing MLOps" | Blog Things I've Learned About Terraform That I Keep Telling People About | Blog Analyzing Unsplash Photo Performance with Python | Blog Analyzing Unsplash Photo Performance with Python I am a Top Mentor on MentorCruise! 🎉 | Blog CI/CD Observability on GitHub Actions and the Role of OpenTelemetry | Blog CI/CD Observability on GitHub Actions and the Role of OpenTelemetry Silent Water Sentinel | Photography Three Early Crosses | Photography Fiery Twilight Trails | Photography Forested Folds Flowing | Photography Majestic Snowbound Spire | Photography Shrouded Winter Peaks | Photography Space Cat Pillar | Photography I am a CNCF (Cloud Native Computing Foundation) Ambassador! | Blog Curved Valley Mist | Photography Highly Independent Tree | Photography Misty Morning Plateau | Photography Sick Shadows Fading | Photography Half Moon Blossom | Photography Serene Pedestal Swinging | Photography Sunset Clouds Reeling | Photography Aerial Nose Parking | Photography How to Structure C Projects: These Best Practices Worked for Me | Blog I'm on the KubeFM Podcast Talking About "Linux Containers From Scratch" | Blog I am (again) a Google Developers Expert! | Blog How to Configure OIDC with Terraform for GitHub Enterprise Server | Blog How to Configure OIDC with Terraform for GitHub Enterprise Server Modern Frontend Development: A Tooling Overview for Engineers Revisiting the Field | Blog Meet verto.sh: Your Gateway to Open-Source Collaboration. | Blog Crafting a Clean, Maintainable, and Understandable Makefile for a C Project. | Blog Crafting a Clean, Maintainable, and Understandable Makefile for a C Project. | Blog barco: Linux Containers From Scratch in C. | Blog barco: Linux Containers From Scratch in C. | Blog How to Create a Release With Multiple Artifacts From a GitHub Actions Workflow Using the Matrix Strategy | Blog How to Create a Release With Multiple Artifacts From a GitHub Actions Workflow Using the Matrix Strategy | Blog How Databases Store and Retrieve Data with B-Trees | Blog How Databases Store and Retrieve Data with B-Trees | Blog Concurrency in Go: Goroutines, Channels, Mutexes, and More | Blog Concurrency in Go: Goroutines, Channels, Mutexes, and More | Blog Club Cloud 2021: Cloud Engineering Panel Discussion | Blog Club Cloud 2021: Cloud Engineering Panel Discussion | Blog How to Prepare for the Google Cloud Engineer Associate Certification Exam | Blog How to Prepare for the Google Cloud Engineer Associate Certification Exam | Blog What is Google Cloud Deploy? | Blog What is GitOps? | Blog Club Cloud Stories #2 - News from Around the Cloud | Blog Club Cloud Stories #2 - News from Around the Cloud | Blog Club Cloud Stories #1 - The First Episode with Antoni Tzavelas & Mark van Holsteijn | Blog Club Cloud Stories #1 - The First Episode with Antoni Tzavelas & Mark van Holsteijn | Blog Quiet Oak Shining | Photography How to Read Firestore Events with Cloud Functions and Golang | Blog How to Read Firestore Events with Cloud Functions and Golang | Blog Google Cloud Pub/Sub vs NATS: An Easy-to-Understand Comparison | Blog Google Cloud Pub/Sub vs NATS: An Easy-to-Understand Comparison | Blog How to Deploy a Multi-cluster Service Mesh on GKE with Anthos | Blog How to Deploy a Multi-cluster Service Mesh on GKE with Anthos | Blog How to Safely Store Secrets in Terraform Using Cloud KMS | Blog How to Safely Store Secrets in Terraform Using Cloud KMS | Blog Designing Serverless Applications on AWS - Jacco Kulman and Luca Cavallin @ End2End LIVE | Blog Designing Serverless Applications on AWS - Jacco Kulman and Luca Cavallin @ End2End LIVE | Blog How to Use Terraform Workspaces to Manage Environment-based Configuration | Blog How to Use Terraform Workspaces to Manage Environment-based Configuration | Blog Puffy Steel Spreading | Photography How to Deploy ElasticSearch on GKE using Terraform and Helm | Blog How to Deploy ElasticSearch on GKE using Terraform and Helm | Blog Summer Windmills Spinning | Photography How to Optimize PHP Performance on Google Cloud Run | Blog How to Optimize PHP Performance on Google Cloud Run | Blog Foggy Boats Rusting | Photography How I Prepared for the Google Cloud Associate Cloud Engineer Exam | Blog How I Prepared for the Google Cloud Associate Cloud Engineer Exam | Blog Winter Kids Chasing | Photography
How to Structure C Projects: These Best Practices Worked for Me
Luca Cavallin · 2024-03-06 · via Luca Cavallin

I recently worked on two different C projects, and I wanted to structure them in a way that would make them easy to maintain and understand. I also wanted to make sure that the projects were easy to build and test. In this post, I will share my experience and the best practices I found for structuring C projects.

You can find the source code for the projects I worked on in the following repositories:

Because of my limited experience with C - I hadn't written any in 10 years - I had to do a lot of research to find out what the current consensus is when it comes to directory layout. I found a lot of useful information on GitHub, Reddit, and Stack Overflow. I also looked at the source code of some popular open-source C projects to see how they were structured. I found that most of the projects I looked at followed a similar layout, and I decided to use that as a starting point.

What The Internet People Say

If you Google for "c project structure best practices" you'll get about 583.000.000 results - no need to worry about doing your own research - I read all of those pages, twice. While opinions vary, there are some common themes that come up again and again. Two approaches are particularly popular:

  • The "modular" approach: This is the most common approach for large projects. The idea is to split the project into multiple directories, each containing a different module of the project. Each module has its own header files, source files, and tests. This approach makes it easy to find the code you're looking for and makes it easy to test individual modules in isolation. This is the way the Linux kernel is structured, for example.
  • The "flat" approach: This approach is more common for small projects and it focuses on keeping the project as simple as possible and yet well-organized.

Since the projects I worked on were relatively small, I decided to use the "flat" approach, which I am going to describe next.

My Approach to Structuring C Projects

After going through all the 583.000.000 results twice, I settled on the following directory layout for my projects:

├── .devcontainer           configuration for GitHub Codespaces
├── .github                 configuration GitHub Actions and other GitHub features
├── .vscode                 configuration for Visual Studio Code
├── bin                     the executable (created by make)
├── build                   intermediate build files e.g. *.o (created by make)
├── docs                    documentation
├── include                 header files
├── lib                     third-party libraries
├── scripts                 scripts for setup and other tasks
├── src                     C source files
│   ├── main.c             (main) Entry point for the CLI
│   └── *.c
├── tests                   contains tests
├── .clang-format           configuration for the formatter
├── .clang-tidy             configuration for the linter
├── .gitignore
├── compile_commands.json   compilation database for clang tools
├── LICENSE
├── Makefile
└── README.md

Let's dive deeper into this layout. We can ignore .devcontainer, .github, .vscode and scripts for now, as they are specific to my development environment and not relevant to the project structure. The files .clang-format and .clang-tidy are configuration files for the Clang formatter and linter, respectively. The compile_commands.json file is a compilation database for Clang tools. These files are not strictly necessary, but they can be useful if you want to use Clang tools in your project. LICENSE and README.md are self-explanatory, and Makefile needs no introduction either, although you can read more about how I wrote mine in Crafting a Clean, Maintainable, and Understandable Makefile for a C Project.

Before we get into the more important details, let's get a few more directories out-of-the-way:

  • The bin directory contains the executable that is created when you run make.
  • The build directory contains the intermediate build files, such as .o files.
  • The docs directory contains the documentation for the project.

Let's spend some time on the src, include, lib, and tests directories.

The src Directory

The src directory contains the C source files for the project, and you will spend most of your time here. I decided to keep it simple using a flat layout. Besides the main.c file which works as the entry file of the program, I split the rest of the code based on "concerns" and data-structures. For example, in the gnaro project:

  • btree.c: contains the implementation of a B-tree data structure.
  • cursor.c: contains the implementation of a cursor for reading and writing to the database.
  • database.c: contains the implementation of the database.
  • pager.c: contains the implementation of the pager.
  • row.c: contains the implementation of a row in the database.
  • input.c, meta.c and statement.c: contain logic needed to parse and prepare user input.

I found this simple layout to be easy to understand and navigate. It also makes it easy to find the code you're looking for, as long as you make an active effort to keep the files small and focused. The downside of this approach is that you will need to keep the Makefile updated with the new files you add to the project so that they are compiled and linked correctly. Given the small size of the projects I worked on, I didn't find this to be a problem, but it could be a problem for others.

The include Directory

The include directory contains the header files for the project. Most if not all of the .c files in the src directory will have a corresponding .h file in the include directory. The header files should contain the public API for the module, and the source files should contain the implementation. This makes it easy to see what the module does without having to look at the implementation. It also makes it easy to test the module in isolation, as you can just include the header file in your test file.

Once again, using the gnaro project as an example:

  • btree.h: included in src/btree.c, defines the public API for the B-tree data structure.
  • cursor.h: included in src/cursor.c, defines the public API for the cursor used to read and write from and to the database.
  • database.h: included in src/database.c, defines the public API for the database implementation.
  • pager.h: included in src/pager.c, defines the public API for the paging logic.
  • row.h: included in src/row.c, defines the public API for the database's row structure.
  • input.h, meta.h and statement.h: included in src/input.c and in src/meta.c and in src/statement.c, defines the public API for handling user input.

Just like the src directory, the downside of this layout is that header files should be referenced in the Makefile so that they are included in the compilation process.

The lib Directory

The lib directory contains third-party libraries that the project depends on. For example, lucavallin/gnaro makes use of the argtable and log.c libraries for parsing CLI arguments and logging, respectively.

There is not much to say about this directory. It's just a place to put your dependencies. Again, don't forget to include these in the Makefile as well, by the way.

The tests Directory

The tests directory contains the tests for the project. I used the CUnit library for testing, and I found it to be a good fit for my needs. The tests directory contains a test file for each module in the src directory. For example, in the gnaro project, the tests directory cointains gnaro_test.c file which is meant to test whatever logic defined in src/gnaro.c.

At this time, in pratice, the file only contains the code needed to setup the tests as recommended by the CUnit documentation. While the tests run, I actually never followed-up on writing useful checks for gnaro and barco since they're just side-, hobby-projects.

Conclusion

Thanks for reading this far! I hope you found this article useful. I know that the project layout I've described is not the only way to organize a C project, but it's the way that I've found to be the most effective for me. The bin, build, docs, script, and ".something" directories are helpful for development purposes, but it is in the src, include, lib, and tests directories where the real work happens.

  • src: contains the C source files for the project.
  • include: contains the header files for the project, included by .c files in src.
  • lib: contains third-party libraries that the project depends on.
  • tests: contains the tests for the project.

The Makefile is the glue that holds everything together and it must be updated to include new files and dependencies. While more modern build systems like CMake and Meson are available, I found that a simple Makefile was sufficient for my needs.

I hope that you can take some of the ideas I've presented here and apply them to your own projects. If you have any questions or comments, feel free to reach out!