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;
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值