C_如何组织C语言的源文件结构/头文件内容组织/链接编译多个文件/引用外部其他源文件中的对象的方法#include & extern/extern的主要作用

C_引用外部其他源文件中的对象的方法include & extern/extern的主要作用

概要

  • #include将被include的文件导入到使用#include的文件中
    • 尽管如此,如果使用了预编译处理(preprocessor),引入的全部内容可能只有一部分会被编译(如果在本次编译中的多个c源文件中有超过一个源文件include过相同的头文件)
  • extern:extern对于源文件中定义的对象的做作用域和static恰好相反

试验代码(链接编译多个文件)

使用extern,允许你在不通过include的情况下使用其他文件中定义的对象

  • 注意,在编译的时候,需要连同main文件(即,定义main())的源文件)以及定义了被引用对象的源文件一起告诉gcc

    • gcc m.c b.c multiply.c -o mbm

      • 本试验指定了主程序源文件m.c

        • 主程序中,通过#include将头文件cxxu.h导入
      • 两个提供函数和外部变量的源文件b.c&multiply.c

      • cxxu.h(该头文件中包含了一些调试宏)

        // 数值调试宏
        #ifndef CXXU
        #define CXXU 1
        #define dprint(expr) printf(#expr " = %d\n", expr)
        #define gprint(expr) printf(#expr " = %g\n", expr)
        #define fprint(expr) printf(#expr " = %f\n", expr)
        #define sprint(expr) printf(#expr " = %s\n", expr)
        #endif
      • multiply.c

        • #ifndef MULTIPLY
          #define MULTIPLY
          #include <stdio.h>
          int multiply(int a, int b)
          {
          return a * b;
          }
          #endif
      • b.c

        • #include <stdio.h>
          const int num = 5;
          const char *str_multiplier="multiplier";
          void func()
          {
          printf("fun in a.c\n");
          }
      • m.c(定义程序入口(main()函数))

        • #include <stdio.h>
          #include "cxxu.h"
          int main()
          {
          //这里将extern声明写在了m.c文件中,当然,也可以将他们放到导入的头文件中(编译语句命令行不变)
          extern void func();
          extern int multiply(int a, int b);
          extern char *str_multiplier;
          // 调用func()打印出实际定义函数体的源文件(b.c 文件中)
          func();
          // multiply()定义在multiply.c文件中.
          int product = multiply(1, 5);
          // 打印调用结果(乘积)
          dprint(product);
          // 打印外部字符串
          sprint(str_multiplier);
          return 0;
          }
      • 运行程序

        • ./mbm
      • 结果

        • ┌─[cxxu@cxxuAli] - [~/cppCodes] - [2022-04-23 09:12:22]
          └─[0] <git:(master dc0fc40✗✱✈) > gcc m.c b.c multiply.c -o mbm
          ┌─[cxxu@cxxuAli] - [~/cppCodes] - [2022-04-23 09:17:37]
          └─[0] <git:(master dc0fc40✗✱✈) > ./mbm
          fun in a.c
          product = 5
          str_multiplier = multiplier
      • 当然,一般将extern 所声明的内容写在某个头文件中,可以保持main.c的整洁,以及提高方便复用

      • 使用extern,可以只把定义对象的文件中的指定对象(通过声明&名称)引入到本程序的编译,而不会访问过多的内容,导致异常(例如对象冲突),在大程序中尤为如此

  • 如果既没有用#include将提供被引用对象的定义的源文件导入,又没有使用extern 在主程序中显示声明,那么即使在编译文件中提到所有文件,还是无法通过编译(找不到被引用的对象)

组织C语言的源文件结构/头文件内容组织

以下简单提一下基本的组织原则,具体的可以参考tlpi(The Linux Programming Interface)源代码中的组织方式

The C(K&R)中也简单提到过C程序源码的组织方式

  • 头文件.h只在其中编写最公用的内容(#include,#define,extern 函数原型的声明)(而不要在头文件中编写函数的实现,函数(函数家族)的实现应当创建相应的.c文件作为库(lib文件),(使用这些库文件(中的函数)的方式不一)
  • 编写头文件的时候,建议用预编译处理来包裹全部的头文件内容,放置重复#include

示例

get_num.h
/*************************************************************************\
* Copyright (C) Michael Kerrisk, 2022. *
* *
* This program is free software. You may use, modify, and redistribute it *
* under the terms of the GNU Lesser General Public License as published *
* by the Free Software Foundation, either version 3 or (at your option) *
* any later version. This program is distributed without any warranty. *
* See the files COPYING.lgpl-v3 and COPYING.gpl-v3 for details. *
\*************************************************************************/
/* Listing 3-5 */
/* get_num.h
Header file for get_num.c.
*/
#ifndef GET_NUM_H
#define GET_NUM_H
#define GN_NONNEG 01 /* Value must be >= 0 */
#define GN_GT_0 02 /* Value must be > 0 */
/* By default, integers are decimal */
#define GN_ANY_BASE 0100 /* Can use any base - like strtol(3) */
#define GN_BASE_8 0200 /* Value is expressed in octal */
#define GN_BASE_16 0400 /* Value is expressed in hexadecimal */
long getLong(const char *arg, int flags, const char *name);
int getInt(const char *arg, int flags, const char *name);
#endif
get_num.c
/*************************************************************************\
* Copyright (C) Michael Kerrisk, 2022. *
* *
* This program is free software. You may use, modify, and redistribute it *
* under the terms of the GNU Lesser General Public License as published *
* by the Free Software Foundation, either version 3 or (at your option) *
* any later version. This program is distributed without any warranty. *
* See the files COPYING.lgpl-v3 and COPYING.gpl-v3 for details. *
\*************************************************************************/
/* Listing 3-6 */
/* get_num.c
Functions to process numeric command-line arguments.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <limits.h>
#include <errno.h>
#include "get_num.h"
/* Print a diagnostic message that contains a function name ('fname'),
the value of a command-line argument ('arg'), the name of that
command-line argument ('name'), and a diagnostic error message ('msg'). */
static void
gnFail(const char *fname, const char *msg, const char *arg, const char *name)
{
fprintf(stderr, "%s error", fname);
if (name != NULL)
fprintf(stderr, " (in %s)", name);
fprintf(stderr, ": %s\n", msg);
if (arg != NULL && *arg != '\0')a
fprintf(stderr, " offending text: %s\n", arg);
exit(EXIT_FAILURE);
}
/* Convert a numeric command-line argument ('arg') into a long integer,
returned as the function result. 'flags' is a bit mask of flags controlling
how the conversion is done and what diagnostic checks are performed on the
numeric result; see get_num.h for details.
'fname' is the name of our caller, and 'name' is the name associated with
the command-line argument 'arg'. 'fname' and 'name' are used to print a
diagnostic message in case an error is detected when processing 'arg'. */
static long
getNum(const char *fname, const char *arg, int flags, const char *name)
{
long res;
char *endptr;
int base;
if (arg == NULL || *arg == '\0')
gnFail(fname, "null or empty string", arg, name);
base = (flags & GN_ANY_BASE) ? 0 : (flags & GN_BASE_8) ? 8
: (flags & GN_BASE_16) ? 16
: 10;
errno = 0;
res = strtol(arg, &endptr, base);
if (errno != 0)
gnFail(fname, "strtol() failed", arg, name);
if (*endptr != '\0')
gnFail(fname, "nonnumeric characters", arg, name);
if ((flags & GN_NONNEG) && res < 0)
gnFail(fname, "negative value not allowed", arg, name);
if ((flags & GN_GT_0) && res <= 0)
gnFail(fname, "value must be > 0", arg, name);
return res;
}
/* Convert a numeric command-line argument string to a long integer. See the
comments for getNum() for a description of the arguments to this function. */
long getLong(const char *arg, int flags, const char *name)
{
return getNum("getLong", arg, flags, name);
}
/* Convert a numeric command-line argument string to an integer. See the
comments for getNum() for a description of the arguments to this function. */
int getInt(const char *arg, int flags, const char *name)
{
long res;
res = getNum("getInt", arg, flags, name);
if (res > INT_MAX || res < INT_MIN)
gnFail("getInt", "integer out of range", arg, name);
return res;
}
posted @   xuchaoxin1375  阅读(13)  评论(0编辑  收藏  举报  
相关博文:
阅读排行:
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
历史上的今天:
2021-04-23 android studio4.+_修改项目使用的sdk版本/sdk列表sdk下载
点击右上角即可分享
微信分享提示