精华内容
下载资源
问答
  • C语言内部函数的一点疑问 如果一个函数只能被本文件中其他函数所调用,他称为内部函数。在定义内部函数时,在函数名和函数类型的前面加static,即:  static 类型标志符 函数名 (形参表); 内部函数又...

     C语言内部函数的一点疑问

    如果一个函数只能被本文件中其他函数所调用,他称为内部函数。在定义内部函数时,在函数名和函数类型的前面加static,即:

        static  类型标志符  函数名  (形参表);

    内部函数又称为静态函数。在定义函数时,如果在函数首部的最左端加关键字extern ,则表示此函数是外部函数,可供其他文件调用。往往extern是缺省的。

      如果我在同一个cpp文件中定义了一个静态函数,现在我想在main函数里面调用它。且静态函数的位置在main函数的下面。问如何声明???

    如:

    #include"stdio.h"
    void main(int argc,char **argv){

     void enter_string(char *str);//目前的代码是有问题的,如何声明???
     char c;
     char str[80];
     enter_string(str);//输入字符串

    printf("%s",str);
    }

     


    static void enter_string(char *str){
     printf("输入字符串:");
     scanf("%s",str);
     


    }

     

     

     

     

    展开全文
  • C 语言变参函数解析

    2014-11-18 15:57:29
    C 语言变参函数解析 1 函数声明  首先,要实现类似printf()的变参函数函数的最后一个参数要用 "..." 表示,如  int log(char * arg1, ...) 这样编译器才能知道这个函数是变参函数。这个参数与变参函数内部...

    1 函数声明
       首先,要实现类似printf()的变参函数,函数的最后一个参数要用 "..." 表示,如
         int log(char * arg1, ...)
    这样编译器才能知道这个函数是变参函数。这个参数与变参函数的内部实现完全没有关系,只是让编译器在编译调用此类函数的语句时不计较参数多少老老实实地把全部参数压栈而不报错,当然...之前至少要有一个普通的参数,这是由实现手段限制的。
    2 函数实现
       C语言通过几个宏实现变参的寻址。下面是linux2.18内核源码里这几个宏的定义,相信符合C89,C99标准的C语言基本都是这样定义的。
        #include<stdarg.h>
        
       typedef char *va_list;

    /*
       Storage alignment properties -- 堆栈按机器字对齐
    */
    #define _AUPBND            (sizeof (acpi_native_uint) - 1)
    #define _ADNBND            (sizeof (acpi_native_uint) - 1)

    /*
       Variable argument list macro definitions -- 变参函数内部实现需要用到的宏
    */
    #define _bnd(X, bnd)          (((sizeof (X)) + (bnd)) & (~(bnd)))
    #define va_arg(ap, T)        (*(T *)(((ap) += (_bnd (T, _AUPBND))) - (_bnd (T,_ADNBND))))
    #define va_end(ap)          (void) 0
    #define va_start(ap, A)        (void) ((ap) = (((char *) &(A)) + (_bnd (A,_AUPBND))))

       下面以x86 32位机为例分析这几个宏的用途
       要理解这几个宏需要对C语言如何传递参数有一定了解。与PASCAL相反,与stdcall 相同,C语言传递参数时是用push指令从右到左将参数逐个压栈,因此C语言里通过栈指针来访问参数。虽然X86的push一次可以压2,4或8个字节入 栈,C语言在压参数入栈时仍然是机器字的size为最小单位的,也就是说参数的地址都是字对齐的,这就是_bnd(X,bnd)存在的原因。另外补充一点 常识,不管是汇编还是C,编译出的X86函数一般在进入函数体后立即执行
       push ebp
       mov ebp, esp
       这两条指令。首先把ebp入栈,然后将当前栈指针赋给ebp,以后访问栈里的参数都使用ebp作为基指针。
     
       一一解释这几个宏的作用。
       _bnd(X,bnd) ,计算类型为X的参数在栈中占据的字节数,当然是字对齐后的字节数了。acpi_native_unit是一个机器字,32位机的定义是:typedef u32 acpi_native_uint;
       显然,_AUPBND ,_ADNBND 的值是 4-1 == 3 == 0x00000003 ,按位取反( ~(bnd))就是0xfffffffc 。
    因此,_bnd(X,bnd) 宏在32位机下就是
       ( (sizeof(X) + 3)&0xfffffffc )
    很明显,其作用是--倘若sizeof(X)不是4的整数倍,去余加4。
       _bnd(sizeof(char),3) == 4
       _bnd(sizeof(struct size7struct),3) == 8

       va_start(ap,A) ,初始化参数指针ap,将函数参数A右边第一个参数的地址赋给ap。 A必须是一个参数的指针,所以此种类型函数至少要有一个普通的参数啊。像下面的例子函数,就是将第二个参数的指针赋给ap。

       va_arg(ap,T) ,获得ap指向参数的值,并使ap指向下一个参数,T用来指明当前参数类型。
       注意((ap) += (_bnd (T, _AUPBND))) 是被一对括号括起来的,然后才减去(_bnd (T, _ADNBND),
    而_AUPBND和_ADNBND是相等的。所以取得的值是ap当前指向的参数值,但是先给ap加了当前参数在字对齐后所占的字节数,使其指向了下一个参数。

    va_end(ap), 作用是美观。
     
    3 总结
    先用一个"..."参数声明函数是变参函数,接下来在函数内部以va_start(ap,A)宏初始化参数指针,然后就可以用va_arg(ap,类型)从左到右逐个获取参数值了


    4、在编程中应该注意的问题和解决办法
        虽然可以通过在堆栈中遍历参数列表来读出所有的可变参数,但是由于不知道可变参数
    多少个,什么时候应该结束遍历,如果在堆栈中遍历太多,那么很可能读取一些无效的数据.
    解决办法:
    a.可以在第一个起始参数中指定参数个数,那么就可以在循环还中读取所有的可变参数;
    b.定义一个结束标记,在调用函数的时候,在最后一个参数中传递这个标记,这样在遍历
      可变参数的时候,可以根据这个标记结束可变参数的遍历;
    下面是一段相关示例代码:
    #include <stdarg.h>
    #include <stdio.h>

    int sum(int, ...);

    int main()
    {
       printf("Sum of 15 and 56 = %d\n",  sum(2, 15, 56) );
       return 0;
    }

    int sum(int num_args, ...)
    {
       int val = 0;
       va_list ap;
       int i;

       va_start(ap, num_args);
       for(i = 0; i < num_args; i++)
       {
          val += va_arg(ap, int);
       }
       va_end(ap);
     
       return val;
    }
     
    虽然可以根据上面两个办法解决读取参数个数的问题,但是如果参数类型都是不定的,
    该怎么办,如果不知道参数的类型,即使读到了参数也没有办法进行处理.

    解决办法:可以自定义一些可能出现的参数类型,这样在可变参数列表中,传入可变参
    数列表中的那类型,然后根据类型,读取可变参数值,并进行准确地转换.传递参数的
    时候可以这样传递:参数数目,可变参数类型1,可变参数值1,可变参数类型2,可变参数值2,....
    看一个类似的例子:
    int log(int arg_num,...)
    {
        va_list ap;
        int d;
        char c, *p, *s;

        va_start(ap, fmt);
        for(i = 0; i < num; i++)
        {
            switch(va_arg(ap, char *))
            {
                case 's':        /* string */
                s = va_arg(ap, char *);
                printf("string %s/n", s);
            break;
           
            case 'd':        /* int */
                d = va_arg(ap, int);
                printf("int %d/n", d);
            break;
           
            case 'c':        /* char */
                c = va_arg(ap, char);
                printf("char %c/n", c);
            break;
           
            default:
                 return -1;    /*err*/
            break;
            }
        }
        va_end(ap);
    }

    展开全文
  • 变量的声明与定义:Int a;是定义型声明,既是声明又是定义,需要分配存储空间Extern a;...,可以在外部文件中扩展,也可以在同一文件中扩展(如函数B在调用的时候使用了a,但a还没定义)(当编译的时候,遇到e...

    变量的声明与定义:

    • Int a;是定义型声明,既是声明又是定义,需要分配存储空间
    • Extern a;是引用型声明,只是声明,不需要分配存储空间

    1、全局变量与局部变量

    全局变量:

    • 编译时分配内存,放在静态存储区里,习惯:首字母大写;。
    • 可以使用extern扩展全局变量的作用域,如extern a;,可以在外部文件中扩展,也可以在同一文件中扩展(如函数B在调用的时候使用了a,但a还没定义)

    (当编译的时候,遇到extern时,会先从本文件中查找全局变量的定义,如果找到,就在本文件中扩展作用域;如果没有找到,就在链接的时候,从外部文件中查找全局变量的定义,如果找到,就将作用域扩展到本文件,否则报错)

    • 静态的全局变量:全局变量使用static修饰,仅限于本文件中使用

    局部变量:

    • 在需要的时候分配内存
    • 存储类别:auto(动态存储区)、static(静态存储区)、(register(在CPU的寄存器里,更快))
    • 静态局部变量:static int a,虽然调用结束后仍然存在,但其他函数不能引用,因为它是局部变量。

    2、全局函数与内部函数

    • 函数默认是全局的,可以被其他文件中的函数调用,当外部需要引用该函数的时候,需要使用函数的原型声明一下,如extern int f(int a);,

      函数原型通知系统,该函数在本文件中稍后定义,或在其他文件中定义。

    • 可以被外部文件引用,也可在在本文件中内其他函数引用(如果用户自定义的函数在主调函数的后面,应该再主调函数中对被调函数做声明。也可进行外部的声明(在文件的开头声明))
    • 内部函数(静态函数):只能在本文件中调用,Static void f(int a)

    空函数:在程序设计的第一阶段,主要完成基本的功能,对一些锦上添花的模块,可以先定义一个空函数,以后再完善它。

    在定义函数时指定的函数类型一般和return语句的表达式类型一致,如果不一致,以函数类型为准。

    
    展开全文
  • From PgsqlWikiC语言函数用户定义的函数可以用 C 写(或者是那些可以与 C 兼容的语言,比如 C++)。...(因此,标准的内部函数库为写用户定义 C 函数提供了大量最好的样例。)目前对 C 函数有两种调...

    From PgsqlWiki

    C语言函数

    用户定义的函数可以用 C 写(或者是那些可以与 C 兼容的语言,比如 C++)。这样的函数是编译成可动态装载的对象的(也叫做共享库),并且是由服务器根据需要装载的。动态装载的特性是"C 语言"函数和"内部"函数之间相互区别的地方 — 而实际上的两者的编码习惯是一样的。(因此,标准的内部函数库为写用户定义 C 函数提供了大量最好的样例。)

    目前对 C 函数有两种调用传统。新的"版本 1"的调用风格是通过为该函数书写一个 PG_FUNCTION_INFO_V1() 宏来标识的,象下面演示的那样。缺少这个宏标识一个老风格的("版本 0")函数。两种情况下里在CREATE FUNCTION里声明的都是语言名字 C。现在老风格的函数已经废弃了,主要是因为移植性原因和缺乏功能,不过出于兼容性原因,系统仍然支持它。

    动态装载

    当某个可装载对象文件里的用户定义的函数第一次被某个服务器会话调用时,动态装载器把函数的目标文件装载入内存以便调用该函数。因此,给用户定义的 C 函数用的 CREATE FUNCTION 语句必须为函数声明两部分信息:可装载的对象文件名字,和所声明的在那个目标文件里调用的函数的 C 名字(联接符号)。如果没有明确声明C名字,那么就假设它与SQL函数名相同。

    基于在 CREATE FUNCTION 命令中给出的名字, 下面的算法用于定位共享对象文件:

    如果名字是一个绝对路径名,则装载给出的文件。

    如果名字以字串 $libdir 开头, 那么该部分将被PostgreSQL库目录名代替, 该目录是在制作的时候判定的。

    如果名字不包含目录部分,那么在配置变量 dynamic_library_path 里声明的路径里查找。

    否则(没有在路径里找到该文件,或者它包含一个非绝对目录部分),那么动态装载器就会试图拿这个名字来装载,这样几乎可以肯定是要失败的。(依靠当前工作目录是不可靠的。)

    如果这个顺序不管用,那么就给这个给出的名字附加上平台相关的共享库文件名扩展(通常是 .so),然后再重新按照上面的过程来一便。如果还是失败,那么装载失败。

    我们建议用相对于 $libdir 或者通过动态库路径的方法来定位共享库。这样,在升级的时候,如果新版本在另外一个目录,就可以简化一些工作。$libdir 表示的实际路径可以用命令 pg_config --pkglibdir 找出来。

    PostgreSQL 服务器运行时的用户 ID 必须可以遍历路径到达你想装载的文件。一个常见的错误就是把该文件设置为 postgresql 不可读和/或不可执行或者把一个高层目录的权限设置为 postgres 用户不能读和/或执行。

    在任何情况下,在 CREATE FUNCTION 命令里给出的文件名在系统表里是以文本值记录的,因此, 如果需要再次装载,那么会再次运行同一个过程。

    注意:PostgreSQL 不会自动编译一个函数; 在使用 CREATE FUNCTION 命令之前你必须编译它。 参阅 Section 32.9.6 获取更多信息。

    为了避免一个动态装载对象装载到不兼容的服务器里,PostgreSQL 检查该文件是否包含一个有合适内容的“magic block”(标识块)。这么做让服务器可以判断明显的不兼容性,比如从不同的主 PostgreSQL 编译来的代码。从 PostgreSQL 8.2 开始就要求“magic block”(标识块)了。要包含一个标识块,在某个(只要一个就行了)模块的源代码文件里,在包含了头文件 fmgr.h 之后,写下面的东西:#ifdef PG_MODULE_MAGIC

    PG_MODULE_MAGIC;

    #endif

    如果代码不必在 PostgreSQL 8.2 之前的版本里编译,那么可以忽略 #ifdef 测试。

    在第一次使用之后,那么一个动态装载的目标文件就会保持在内存里。在同一次会话中的后继的函数调用将只会产生很小的符号表查询的过热。 如果你需要强制对象文件的重载,比如你重新编译了该文件,那么可以使用LOAD命令或者开始一次新的会话。

    另外,一个动态装载的文件可以包含初始化函数和终止函数。如果该文件包含一个叫做 _PG_init 的函数,那么该函数将在装载该文件后立即调用。这个函数不接受任何参数,并且应该返回空。如果这个文件包含一个叫做 _PG_fini 的函数,那么就会在卸载该文件之前运行该函数。这个函数也是不接受任何参数并且应该返回空。请注意 _PG_fini 只是在卸载文件的时候调用,而不是在进程结束的时候。(目前,卸载只是发生在用 LOAD 命令明确重载该文件的时候才会发生。)

    基本类型的 C 语言函数

    要知道如何写 C 语言的函数,你需要知道 PostgreSQL 在内部是如何表现基本数据类型的,以及它们是如何传入函数以及传出函数的。 PostgreSQL 内部把基本类型当作"一片内存"看待。定义在某种类型上的用户定义函数实际上定义了 PostgreSQL 对(该数据类型)可能的操作。 也就是说,PostgreSQL 只是从磁盘读取和存储该数据类型,而使用你定义的函数来输入,处理和输出数据。基本类型可以有下面三种内部形态(格式)之一:

    传递数值,定长

    传递引用,定长

    传递引用,变长

    传递数值的类型的长度只能是1,2 或 4 字节。(还有 8 字节,如果 sizeof(Datum) 在你的机器上是 8 的话)。你要仔细定义你的类型,确保它们在任何体系平台上都是相同尺寸(字节)。例如,long 型是一个危险的类型,因为在一些机器上它是 4 字节而在另外一些机器上是 8 字节,而 int型在大多数 Unix 机器上都是4字节的。在一个 Unix 机器上的 int4 合理的实现可能是:/* 4-字节整数,传值 */

    typedef int integer;

    另外,任何尺寸的定长类型都可以是传递引用型。例如,下面是一个 PostgreSQL 类型的实现:/* 16-字节结构,传递引用 */

    typedef struct

    {

    double x, y;

    } Point;

    只能使用指向这些类型的指针来在 PostgreSQL 函数里传入和传出数据。要返回这样的类型的值,用 palloc() 分配正确数量的存储器,填充这些存储器,然后返回一个指向它的指针。(另外,如果你只是想返回一个和某个输入参数类型相同、数值相同的一个数值,你可以忽略额外的 palloc,只要返回指向输入数值的指针就行。)

    最后,所有变长类型同样也只能通过传递引用的方法来传递。所有变长类型必须以一个正好 4 字节长的长度域开始,并且所有存储在该类型的数据必须放在紧接着长度域的存储空间里。长度域是结构的全长,也就是说,包括长度域本身的长度。

    警告:绝对不要修改一个传递引用的输入值的内容。 如果你这么干,那么你很可能破坏磁盘上的数据,因为你给拿到指针很可能直接指向一个磁盘缓冲区。 这条规则的唯一例外在 Section 32.10 里。

    比如,我们可以用下面方法定义一个 text 类型:typedef struct {

    integer length;

    char data[1];

    } text;

    显然,上面声明的数据域的长度不足以存储任何可能的字串。因为在 C中不可能声明变长度的结构,所以我们倚赖这样的知识:C 编译器不会对数组下标进行范围检查。我们只需要分配足够的空间,然后把数组当做已经声明为合适长度的变量访问。(这是一个常用的技巧,你可以在许多 C 的教科书中读到。)

    当处理变长类型时,我们必须仔细分配正确的存储器数量并正确设置长度域。 例如,如果我们想在一个 text 结构里存储 40 字节, 我们可能会使用象下面的代码片段:#include "postgres.h"

    ...

    char buffer[40]; /* 我们的源数据 */

    ...

    text *destination = (text *) palloc(VARHDRSZ + 40);

    destination->length = VARHDRSZ + 40;

    memcpy(destination->data, buffer, 40);

    ...

    VARHDRSZ 和 sizeof(int4) 一样, 但是我们认为用宏 VARHDRSZ 表示附加尺寸是用于变长类型的更好的风格。

    表 34-1 列出了书写一个使用了 PostgreSQL 内置类型的 C 函数里需要的知道的哪个 SQL 类型对应哪个 C 类型。"定义在" 列给出了需要包含以获取该类型定义的头文件(实际的定义可能是在包含在列出的文件所包含的文件中。我们建议用户只使用这里定义的接口。)注意,你应该总是首先包括 postgres.h,因为它声明了许多你需要的东西。

    表 34-1. 与内建的类型等效的 C 类型

    内建类型

    C 类型

    定义在

    abstime

    AbsoluteTime

    utils/nabstime.h

    boolean

    bool

    postgres.h (可能是编译器内置)

    box

    BOX*

    utils/geo_decls.h

    bytea

    bytea*

    postgres.h

    "char"

    char

    (编译器内置)

    character

    BpChar*

    postgres.h

    cid

    CommandId

    postgres.h

    date

    DateADT

    utils/date.h

    smallint (int2)

    int2 或 int16

    postgres.h

    int2vector

    int2vector*

    postgres.h

    integer (int4)

    int4 或 int32

    postgres.h

    real (float4)

    float4*

    postgres.h

    double precision (float8)

    float8*

    postgres.h

    interval

    Interval*

    utils/timestamp.h

    lseg

    LSEG*

    utils/geo_decls.h

    name

    Name

    postgres.h

    oid

    Oid

    postgres.h

    oidvector

    oidvector*

    postgres.h

    path

    PATH*

    utils/geo_decls.h

    point

    POINT*

    utils/geo_decls.h

    regproc

    regproc

    postgres.h

    reltime

    RelativeTime

    utils/nabstime.h

    text

    text*

    postgres.h

    tid

    ItemPointer

    storage/itemptr.h

    time

    TimeADT

    utils/date.h

    time with time zone

    TimeTzADT

    utils/date.h

    timestamp

    Timestamp*

    utils/timestamp.h

    tinterval

    TimeInterval

    utils/nabstime.h

    varchar

    VarChar*

    postgres.h

    xid

    TransactionId

    postgres.h

    既然我们已经讨论了基本类型所有的可能结构,我们便可以用实际的函数举一些例子。

    语言函数的版本-0 调用风格

    我们先提供"老风格"的调用风格 — 尽管这种做法现在已经不提倡了,但它还是比较容易迈出第一步。在版本-0方法里,C 函数的参数和结果只是用普通 C 风格声明,但是要小心使用上面显示的SQL数据类型的 C 表现形式。

    下面是一些例子:#include "postgres.h"

    #include /* 传递数值 */

    int

    add_one(int arg)

    {

    return arg + 1;

    }

    /* 传递引用,定长 */

    float8 *

    add_one_float8(float8 *arg)

    {

    float8 *result = (float8 *) palloc(sizeof(float8));

    *result = *arg + 1.0;

    return result;

    }

    Point *

    makepoint(Point *pointx, Point *pointy)

    {

    Point *new_point = (Point *) palloc(sizeof(Point));

    new_point->x = pointx->x;

    new_point->y = pointy->y;

    return new_point;

    }

    /* 传递引用,变长 */

    text *

    copytext(text *t)

    {

    /*

    * VARSIZE 是结构以字节计的总长度

    */

    text *new_t = (text *) palloc(VARSIZE(t));

    VARATT_SIZEP(new_t) = VARSIZE(t);

    /*

    * VARDATA 是结构中一个指向数据区的指针

    */

    memcpy((void *) VARDATA(new_t), /* 目的 */

    (void *) VARDATA(t), /* 源 */

    VARSIZE(t)-VARHDRSZ); /* 多少字节 */

    return new_t;

    }

    text *

    concat_text(text *arg1, text *arg2)

    {

    int32 new_text_size = VARSIZE(arg1) + VARSIZE(arg2) - VARHDRSZ;

    text *new_text = (text *) palloc(new_text_size);

    VARATT_SIZEP(new_text) = new_text_size;

    memcpy(VARDATA(new_text), VARDATA(arg1), VARSIZE(arg1)-VARHDRSZ);

    memcpy(VARDATA(new_text) + (VARSIZE(arg1)-VARHDRSZ),

    VARDATA(arg2), VARSIZE(arg2)-VARHDRSZ);

    return new_text;

    }

    假设上面的代码放在文件 funcs.c 并且编译成了共享目标,我们可以用下面的命令为 PostgreSQL 定义这些函数:CREATE FUNCTION add_one(integer) RETURNS integer

    AS 'DIRECTORY/funcs', 'add_one'

    LANGUAGE C STRICT;

    -- 注意:重载了名字为 add_one() 的 SQL 函数

    CREATE FUNCTION add_one(double precision) RETURNS double precision

    AS 'DIRECTORY/funcs', 'add_one_float8'

    LANGUAGE C STRICT;

    CREATE FUNCTION makepoint(point, point) RETURNS point

    AS 'DIRECTORY/funcs', 'makepoint'

    LANGUAGE C STRICT;

    CREATE FUNCTION copytext(text) RETURNS text

    AS 'DIRECTORY/funcs', 'copytext'

    LANGUAGE C STRICT;

    CREATE FUNCTION concat_text(text, text) RETURNS text

    AS 'DIRECTORY/funcs', 'concat_text'

    LANGUAGE C STRICT;

    这里的 DIRECTORY 代表共享库文件的目录 (比如PostgreSQL教程目录,那里包含本节使用的例子的代码)。(更好的风格应该是在加了 DIRECTORY 到搜索路径之后, 在 AS 子句里只使用 'funcs',不管怎样,我们都可以省略和系统相关的共享库扩展,通常是 .so 或 .sl。)

    请注意我们把函数声明为"strict"(严格),意思是说如果任何输入值为 NULL,那么系统应该自动假设一个 NULL 的结果。这样处理可以让我们避免在函数代码里面检查 NULL 输入。如果不这样处理,我们就得明确检查空值,比如为每个传递引用的参数检查空指针。(对于传值类型的参数,我们甚至没有办法检查!)

    尽管这种老风格的调用风格用起来简单,它确不太容易移植;在一些系统上,我们用这种方法传递比 int 小的数据类型就会碰到困难。而且,我们没有很好的返回 NULL 结果的办法,也没有除了把函数严格化以外的处理 NULL 参数的方法。下面要讲的版本-1的方法则解决了这些问题。

    C 语言函数的版本-1调用风格

    版本-1 调用风格依赖宏来消除大多数传递参数和结果的复杂性。版本-1 风格函数的 C 定义总是下面这样Datum funcname(PG_FUNCTION_ARGS)

    另外,下面的宏PG_FUNCTION_INFO_V1(funcname);

    也必须出现在同一个源文件里(通常就可以写在函数自身前面)。 对那些内部-语言函数而言,不需要调用这个宏, 因为PostgreSQL目前假设内部函数都是版本-1。 不过,对于动态链接的函数,它是必须的。

    在版本-1函数里, 每个实际参数都是用一个对应该参数的数据类型的 PG_GETARG_xxx()宏抓取的,结果是用返回类型的 PG_RETURN_xxx()宏返回的。 PG_GETARG_xxx() 接受要抓取的函数参数的编号作为其参数,编号是从 0 开始的。 PG_RETURN_xxx() 接受实际要返回的数值为自身的参数。

    下面是和上面一样的函数,但是是用版本-1风格编的:#include "postgres.h"

    #include #include "fmgr.h"

    /* 传递数值 */

    PG_FUNCTION_INFO_V1(add_one);

    Datum

    add_one(PG_FUNCTION_ARGS)

    {

    int32 arg = PG_GETARG_INT32(0);

    PG_RETURN_INT32(arg + 1);

    }

    /* 传递引用,定长 */

    PG_FUNCTION_INFO_V1(add_one_float8);

    Datum

    add_one_float8 (PG_FUNCTION_ARGS)

    {

    /* 用于 FLOAT8 的宏,隐藏其传递引用的本质 */

    float8 arg = PG_GETARG_FLOAT8(0);

    PG_RETURN_FLOAT8(arg + 1.0);

    }

    PG_FUNCTION_INFO_V1(makepoint);

    Datum

    makepoint(PG_FUNCTION_ARGS)

    {

    /* 这里,我们没有隐藏 Point 的传递引用的本质 */

    Point *pointx = PG_GETARG_POINT_P(0);

    Point *pointy = PG_GETARG_POINT_P(1);

    Point *new_point = (Point *) palloc(sizeof(Point));

    new_point->x = pointx->x;

    new_point->y = pointy->y;

    PG_RETURN_POINT_P(new_point);

    }

    /* 传递引用,变长 */

    PG_FUNCTION_INFO_V1(copytext);

    Datum

    copytext(PG_FUNCTION_ARGS)

    {

    text *t = PG_GETARG_TEXT_P(0);

    /*

    * VARSIZE 是结构以字节计的总长度

    */

    text *new_t = (text *) palloc(VARSIZE(t));

    SET_VARSIZE(new_t, VARSIZE(t));

    /*

    * VARDATA 是结构中指向数据区的一个指针

    */

    memcpy((void *) VARDATA(new_t), /* 目的 */

    (void *) VARDATA(t), /* 源 */

    VARSIZE(t)-VARHDRSZ); /* 多少字节 */

    PG_RETURN_TEXT_P(new_t);

    }

    PG_FUNCTION_INFO_V1(concat_text);

    Datum

    concat_text(PG_FUNCTION_ARGS)

    {

    text *arg1 = PG_GETARG_TEXT_P(0);

    text *arg2 = PG_GETARG_TEXT_P(1);

    int32 new_text_size = VARSIZE(arg1) + VARSIZE(arg2) - VARHDRSZ;

    text *new_text = (text *) palloc(new_text_size);

    SET_VARSIZE(new_text, new_text_size);

    memcpy(VARDATA(new_text), VARDATA(arg1), VARSIZE(arg1)-VARHDRSZ);

    memcpy(VARDATA(new_text) + (VARSIZE(arg1)-VARHDRSZ),

    VARDATA(arg2), VARSIZE(arg2)-VARHDRSZ);

    PG_RETURN_TEXT_P(new_text);

    }

    用到的 CREATE FUNCTION 命令和用于老风格的等效的命令一样。

    乍一看,版本-1的编码好象只是无目的地蒙人。但是它们的确给我们许多改进,因为宏可以隐藏许多不必要的细节。 一个例子在add_one_float8的编码里,这里我们不再需要不停叮嘱自己 float8 是传递引用类型。 另外一个例子是用于变长类型的宏 GETARG 隐藏了抓取"toasted"(烤炉)(压缩的或者线外存储的)值需要做的处理。

    版本-1的函数另一个巨大的改进是对 NULL 输入和结果的处理。 宏 PG_ARGISNULL(n) 允许一个函数测试每个输入是否为 NULL (当然,这件事只是对那些没有声明为 "strict" 的函数有必要)。 因为如果有PG_GETARG_xxx() 宏,输入参数是从零开始计算的。请注意,除非我们检验过参数不是 NULL,否则我们不应该执行 PG_GETARG_xxx()。 要返回一个 NULL 结果,执行一个 PG_RETURN_NULL(),这样对严格的和不严格的函数都有效。

    在新风格的接口中提供的其它的选项是 PG_GETARG_xxx() 宏的两个变种。第一个, PG_GETARG_xxx_COPY() 保证返回一个指定参数的副本,该副本是可以安全地写入的。 (普通的宏有时候会返回一个指向物理存储在表中的某值的指针,因此我们不能写入该指针。 用 PG_GETARG_xxx_COPY() 宏保证获取一个可写的结果。) 第二个变体由 PG_GETARG_xxx_SLICE() 宏组成,它接受三个参数。第一个是参数的个数(与上同)。 第二个和第三个是要返回的偏移量和数据段的长度。 偏移是从零开始计算的,一个负数的长度则要求返回该值的剩余长度的数据。 这些过程提供了访问大数据值的中部分的更有效的方法,特别是数据的存储类型是"external"的时候。 (一个字段的存储类型可以用 ALTER TABLE tablename ALTER COLUMN colname SET STORAGE storagetype指定。storagetype 是 plain,external, extended 或 main 之一。)

    最后,版本-1的函数调用风格也让我们可能返回一个结果集 (Section 34.9.10)并且实现触发器函数(Chapter 35)和过程语言调用句柄(Chapter 48)。 版本-1 的代码也比版本-0的更容易移植,因为它没有违反 C 标准对函数调用协议的限制。更多的细节请参阅源程序中的src/backend/utils/fmgr/README。

    书写代码

    在我们转到更深的话题之前,我们要先讨论一些PostgreSQLC 语言函数的编码规则。虽然可以用 C 以外的其他语言如书写用于PostgreSQL的共享函数,但通常很麻烦(虽然是完全可能的),因为其他像 C++,FORTRAN,或者 Pascal 这样的语言并不遵循和 C 的调用习惯。也就是说,其他语言与C的传递参数和返回值的方式不一样。因此我们假设你的编程语言函数是用 C 写的。

    书写和制作 C 函数的基本规则如下:

    使用 pg_config --includedir-server 找出PostgreSQL服务器的头文件在你的系统(或者是你的用户所运行的系统)中的安装位置。

    编译和连接你的代码,使之可以动态装在到 PostgreSQL 中,这总是需要一些特殊的标志。参阅34.9.6 节获取如何在你的操作系统里完成这个任务的细节。

    记得为你的共享库定义一个“magic block”(标识块),如我们在34.9.1节说的那样。

    当分配存储器时,用 PostgreSQL 的函数palloc和pfree取代相应的 C 库函数malloc和free。用palloc分配的存储器在每个事务结束时会自动释放,避免了内存泄露。

    用memset对你的结构里的字节清零。如果不这么做,就很难支持 hash 索引和 hash 连接,因为我们必须从自己的数据结构中选出最具特征的位来计算 hash。即使你初始化了你的结构的所有域,仍然有可能有几个对齐字节(结构中的洞)含有垃圾值。

    大多数的 PostgreSQL 内部类型定义在postgres.h,而函数管理器接口(PG_FUNCTION_ARGS等等。)都在fmgr.h,所以你至少应该包括这两个文件。出于移植性原因,最好先包括postgres.h,然后再包括其它系统或者用户头文件。包含了postgres.h将自动包含c.h,elog.h和 palloc.h。

    在目标文件里定义的符号一定不能相互冲突,也不能和定义在 PostgreSQL 服务器可执行代码中的符号名字冲突。如果你看到了与此相关的错误信息,那么你必须给你的函数或者变量重命名。

    编译和链接动态链接的函数

    在你能够使用由 C 写 PostgreSQL 扩展函数之前, 你必须用一种特殊的方法编译和链接它们,这样才能生成可以被服务器动态地装载的文件。 准确地说,我们需要创建一个共享库。

    如果需要超出本节所包含范围的信息,那么你应该阅读你的操作系统的文档, 特别是 C 编译器cc和链接器ld的手册页。另外,PostgreSQL 源代码里包含几个可以运行的例子,它们在contrib目录里。不过,如果你依赖这些例子,那么你自己的模块就会需要 PostgreSQL 源代码的存在才能编译了。

    创建共享库和链接可执行文件类似:首先把源代码编译成目标文件,然后把目标文件链接起来。目标文件需要创建成位置无关码(position-independent code/PIC),概念上就是在可执行程序装载它们的时候,它们可以放在内存里的任何地方,(用于可执行文件的目标文件通常不是用这个方式编译的。)链接动态库的命令包含特殊标志,与链接可执行文件的命令是有区别的。(至少理论上如此 — 在一些系统里会更恶心。)

    在下面的例子里,我们假设你的源程序代码在foo.c文件里并且将创建成名字叫foo.so的共享库。,除非我们另外注明,否则中介的对象文件将叫做foo.o。一个共享库可以包含多个对象文件,不过我们在这里只用一个。

    BSD/OS

    创建 PIC 的编译器标志是 -fpic。创建共享库的链接器标志是 -shared。gcc -fpic -c foo.c

    ld -shared -o foo.so foo.o

    上面方法适用于版本 4.0 的 BSD/OS。

    FreeBSD

    创建 PIC 的编译器标志是 -fpic'。创建共享库的链接器标志是 -shared。gcc -fpic -c foo.c

    gcc -shared -o foo.so foo.o

    上面方法适用于版本 3.0 的 FreeBSD.

    HP-UX

    创建 PIC 的系统编译器标志是 +z。如果使用 GCC 则是 -fpic。 创建共享库的链接器标志是 -b。因此cc +z -c foo.c

    或gcc -fpic -c foo.c

    然后ld -b -o foo.sl foo.o

    HP-UX 使用 .sl 做共享库扩展,和其它大部分系统不同。

    IRIX

    PIC 是缺省,不需要使用特殊的编译器选项。 生成共享库的链接器选项是 -shared。cc -c foo.c

    ld -shared -o foo.so foo.o

    Linux

    创建 PIC 的编译器标志是 -fpic。在一些平台上的一些环境下, 如果-fpic不能用那么必须使用-fPIC。 参考 GCC 的手册获取更多信息。创建共享库的编译器标志是-shared。一个完整的例子看起来象:cc -fpic -c foo.c

    cc -shared -o foo.so foo.o

    MacOS X

    这里是一个例子。这里假设开发工具已经安装好了。cc -c foo.c

    cc -bundle -flat_namespace -undefined suppress -o foo.so foo.o

    NetBSD

    创建 PIC 的编译器标志是 -fpic。对于 ELF 系统, 带-shared标志的编译命令用于链接共享库。 在老的非 ELF 系统里,使用ld -Bshareable。gcc -fpic -c foo.c

    gcc -shared -o foo.so foo.o

    OpenBSD

    创建 PIC 的编译器标志是-fpic。ld -Bshareable 用于链接共享库。gcc -fpic -c foo.c

    ld -Bshareable -o foo.so foo.o

    Solaris

    创建 PIC 的编译器命令是用 Sun 编译器时为-KPIC而用 GCC 时为-fpic。链接共享库时两个编译器都可以用-G或者用 GCC 时还可以是-shared。cc -KPIC -c foo.c

    cc -G -o foo.so foo.o

    或gcc -fpic -c foo.c

    gcc -G -o foo.so foo.o

    Tru64 UNIX

    PIC 是缺省,因此编译命令就是平常的那个。 带特殊选项的ld用于链接:cc -c foo.c

    ld -shared -expect_unresolved '*' -o foo.so foo.o

    用 GCC 代替系统编译器时的过程是一样的;不需要特殊的选项。

    UnixWare

    SCO 编译器创建 PIC 的标志是-K PIC,GCC 是 -fpic。 链接共享库时 SCO 编译器用 -G 而 GCC 用-shared。cc -K PIC -c foo.c

    cc -G -o foo.so foo.o

    或gcc -fpic -c foo.c

    gcc -shared -o foo.so foo.o

    技巧: 如果你觉得这些步骤实在太复杂,那么你应该考虑使用 GNU Libtool,它把平台的差异隐藏在了一个统一的接口里。

    生成的共享库文件然后就可以装载到 PostgreSQL里面去了。在给 CREATE FUNCTION 命令声明文件名的时候,我们必须声明共享库文件的名字而不是中间目标文件的名字。请注意你可以在 CREATE FUNCTION 命令上忽略系统标准的共享库扩展 (通常是.so或.sl),并且出于最佳的兼容性考虑也应该忽略。

    回去看看 Section 34.9.1 获取有关服务器预期在哪里找到共享库的信息。

    扩展的制作架构

    如果你在考虑发布你的PostgreSQL扩展模块,那么给他们设置一个可移植的制作系统可能会相当困难。因此PostgreSQL安装提供了一个用于扩展的制作架构,叫做 PGXS,这样,简单的扩展模块可以在一个已经安装了的服务器上制作了。请注意这个架构并不是企图用于实现一个统一的、可以用于制作所有与PostgreSQL相关的软件的架构;它只是用于自动化那些简单的服务器扩展模块的制作。对于更复杂的包,你还是需要书写自己的制作系统。

    要在你的扩展中使用该架构,你必须写一个简单的 makefile。在该makefile里,你需要设置一些变量并且最后包括全局的 PGXS makefile。 下面是一个制作一个包含一个共享库,一个 SQL 脚本,和一个文档文本文件的叫做isbn_issn的例子:MODULES = isbn_issn

    DATA_built = isbn_issn.sql

    DOCS = README.isbn_issn

    PGXS := $(shell pg_config --pgxs)

    include $(PGXS)

    最后两行应该总是一样的。在文件的前面,你赋予变量或者增加客户化的 make 规则。

    可以设置下列变量:

    MODULES

    一个需要从同词根(同名,不同后缀)的源代码上制作的共享对象的列表(不要在这个列表里包含后缀)

    DATA

    安装到prefix/share/contrib的随机文件

    DATA_built

    需要先制作的,安装到prefix/share/contrib里面的随机文件。

    DOCS

    安装到prefix/doc/contrib里面的随机文件

    SCRIPTS

    安装到prefix/bin里面的脚本文件(非二进制)

    SCRIPTS_built

    安装到prefix/bin里面的,需要先制作的脚本文件(非二进制)。

    REGRESS

    回归测试案例的列表(没有后缀)

    或者最多声明下面两个之一:

    PROGRAM

    一个需要制作的二进制文件(在OBJS里面列出目标文件)

    MODULE_big

    一个需要制作的共享对象(在OBJS里列出目标文件)

    还可以设置下列变量:

    EXTRA_CLEAN

    在make clean里删除的额外的文件

    PG_CPPFLAGS

    将增加到CPPFLAGS

    PG_LIBS

    将增加到PROGRAM链接行里

    SHLIB_LINK

    将增加到MODULE_big连接行里

    PG_CONFIG

    需要安装的包指向的 PostgreSQL 安装的 pg_config 程序的路径(通常是在你的 PATH 中的第一个 pg_config)

    把这个 makefile 以Makefile的名字放在保存你的扩展的目录里。然后你就可以运行make来编译,然后用make install安装你的模块。这个扩展是为你的路径里找到的第一个 pg_config 命令对应的 PostgreSQL 安装编译和安装的。你可以通过在 makefile 里或者在 make 命令行上将PG_CONFIG设置成指向对应的 pg_config 程序来为不同的 PostgreSQL 版本安装。

    警告: 修改 PG_CONFIG 的方法只能在 PostgreSQL 8.3 或者更高版本上使用。老版本除了可以设置为 pg_config 之外,啥用也没有;你必须更改你的路径,选择你需要的那个安装版本。

    在 REGRESS 变量中列出用于对你的模块进行回归测试的脚本,就像 PostgreSQL 服务器的 make installcheck 一样。要想这部分能运转,你必须在你的扩展目录里有一个 sql/ 子目录,在其中你要为每个需要运行的测试组写一个文件,文件的扩展名应该是 .sql,扩展名不应该包含在 makefile 的 REGRESS 列表中。每个测试都应该在一个叫 expected/ 的子目录中包含一个结果文件,其扩展名是 .out。测试是通过运行 make installcheck 执行的,结果会和与其文件进行比较。区别将会以 diff -c 格式写入到 regression.diffs里。请主义,如果试图在缺乏预期文件的情况下运行测试,那么就会报告为“trouble”(问题),所以你要确保自己已经书写了所有预期文件。

    提示: 创建预期文件的最简单方法就是创建一个空文件,然后在执行完测试运行之后,仔细检查结果文件(可以在 result/ 目录找到),然后,如果它们符合你的预期,把它们拷贝到 expected/。

    复合类型的 C 语言函数

    复合类型不象 C 结构那样有固定的布局。 复合类型的实例可能包含空(null)域。 另外,一个属于继承层次一部分的复合类 型可能和同一继承范畴的其他成员有不同的域/字段。 因此,PostgreSQL 提供一个过程接口用于从 C 里面访问复合类型。

    假设我们为下面查询写一个函数SELECT name, c_overpaid(emp, 1500) AS overpaid

    FROM emp

    WHERE name = 'Bill' OR name = 'Sam';

    在上面的查询里,用版本 0 的调用接口,我们可以这样定义c_overpaid:#include "postgres.h"

    #include "executor/executor.h" /* 用 GetAttributeByName() */

    bool

    c_overpaid(HeapTupleHeader t, /* emp 的当前行*/

    int32 limit)

    {

    bool isnull;

    int32 salary;

    salary = DatumGetInt32(GetAttributeByName(t, "salary", &isnull));

    if (isnull)

    return (false);

    return salary > limit;

    }

    在版本-1编码,上面的东西会写成下面这样:#include "postgres.h"

    #include "executor/executor.h" /* 用 GetAttributeByName() */

    PG_FUNCTION_INFO_V1(c_overpaid);

    Datum

    c_overpaid(PG_FUNCTION_ARGS)

    {

    HeapTupleHeader t = PG_GETARG_HEAPTUPLEHEADER(0);

    int32 limit = PG_GETARG_INT32(1);

    bool isnull;

    Datum salary;

    salary = GetAttributeByName(t, "salary", &isnull);

    if (isnull)

    PG_RETURN_BOOL(false);

    /* 另外,我们可能更希望将 PG_RETURN_NULL() 用在空薪水上 */

    PG_RETURN_BOOL(DatumGetInt32(salary) > limit);

    }

    GetAttributeByName 是 PostgreSQL 系统函数, 用来返回当前记录的字段。它有三个参数:类型为 HeapTupleHeader 的传入函数的参数,你想要的字段名称, 以及一个用以确定字段是否为空(null)的返回参数。 GetAttributeByName 函数返回一个Datum值, 你可以用对应的 DatumGetXXX() 宏把它转换成合适的数据类型。 请注意,如果设置了空标志,那么返回值是无意义的, 在准备对结果做任何处理之前,总是要先检查空标志。

    还有一个 GetAttributeByNum,它用字段编号而不是字段名选取目标字段。

    下面的命令在 SQL 里声明 c_overpaid 函数:CREATE FUNCTION c_overpaid(emp, integer)

    RETURNS bool

    AS 'DIRECTORY/funcs', 'c_overpaid'

    LANGUAGE C STRICT;

    请注意我们使用了 STRICT,这样我们就不需要检查输入参数是否有 NULL。

    返回行(复合类型)

    要从一个 C 语言函数里返回一个行或者一个复合类型的数值,我们可以使用一个特殊的 API, 它提供了许多宏和函数来消除大多数制作复合数据类型的复杂性。 要使用该 API,源代码必须包含:#include "funcapi.h"

    制作一个复合类型数据值(也就是一个"元组")有两种方法:你可以从一个Datum值的数组里制作,也可以从一个 C 字串数组里制作:我们可以把这个数组传递给该元组的字段类型的输入转换函数。不管是哪种方式,你首先都需要为元组结构获取或者制作一个TupleDesc描述符。在使用 Datum的时候,你把这个TupleDesc传递给BlessTupleDesc然后为每行调用heap_form_tuple。 在使用 C 字串的时候,你把TupleDesc传递给TupleDescGetAttInMetadata,然后为每行调用BuildTupleFromCStrings。如果是一个函数返回一个元组集合的场合,所有设置步骤都可以在第一次调用该函数的时候一次性完成。

    有几个便利函数可以用于设置所需要的TupleDesc。 在大多数返回复合类型给调用者的函数里建议的做法是这样的:TypeFuncClass get_call_result_type(FunctionCallInfo fcinfo,

    Oid *resultTypeId,

    TupleDesc *resultTupleDesc)

    把传递给调用函数自己的fcinfo传递给他。(这个当然要求你使用版本 1 的调用习惯。)resultTypeId 可以声明为 NULL 或者是接收函数的结果类型 OID 的局部变量的地址(指针)。resultTupleDesc 应该是一个局部的TupleDesc变量的地址(指针)。检查结果是否 TYPEFUNC_COMPOSITE;如是,resultTupleDesc就已经填充好需要的TupleDesc了。(如不是,你可以报告一个类似"返回记录的函数在一个不接受记录的环境中被调用"的错误。)

    提示: get_call_result_type 可以把一个多形的函数结果解析为实际类型;因此它在返回多形的标量结果的函数里也很有用,不仅仅是返回复合类型的函数里。resultTypeId 输出主要是用于那些返回多形的标量类型的函数的。

    注意: get_call_result_type 有一个同胞弟兄 get_expr_result_type,可以用于给一个用表达式树表示的函数调用解析输出。 它可以用于视图从函数本身外边判断结果类型的场合。还有一个 get_func_result_type,可以用在只能拿到函数的 OID 的场合。不过,这些函数不能处理那些声明为返回 record 的函数,并且 get_func_result_type 不能解析多形的类型,因此你最好还是使用 get_call_result_type。

    旧的,现在已经废弃的获取 TupleDesc 的函数是TupleDesc RelationNameGetTupleDesc(const char *relname)

    它可以从一个命名的关系里为行类型获取一个 TupleDesc,还有是TupleDesc TypeGetTupleDesc(Oid typeoid, List *colaliases)

    可以基于类型 OID 获取一个 TupleDesc。它可以用于给一个基本类型或者一个复合类型获取 TupleDesc。不过它不能处理返回 record 的函数,并且不能解析多形的类型。

    一旦你有了一个 TupleDesc,如果你想使用 Datum,那么调用TupleDesc BlessTupleDesc(TupleDesc tupdesc)

    如果你想用 C 字串,那么调用AttInMetadata *TupleDescGetAttInMetadata(TupleDesc tupdesc)

    如果你在写一个返回集合的函数,那么你可以吧这些函数的结果保存在 FuncCallContext 结构里 — 分别使用 tuple_desc 或者 attinmeta 字段。

    在使用 Datum 的时候,使用HeapTuple heap_form_tuple(TupleDesc tupdesc, Datum *values, char *nulls)

    制作一个 HeapTuple,它把数据以 Datum 的形式交给用户。

    在使用 C 字串的时候,用HeapTuple BuildTupleFromCStrings(AttInMetadata *attinmeta, char **values)

    制作一个 HeapTuple,以 C 字串的形式给出用户数据。"values" 是一个 C 字串的数组,返回行的每个字段对应其中一个。每个 C 字串都应该是字段数据类型的输入函数预期的形式。为了从其中一个字段中返回一个空值,values 数组中对应的指针应该设置为 NULL。这个函数将会需要为你返回的每个元组调用一次。

    一旦你制作了一个从你的函数中返回的元组,那么该元组必须转换成一个 Datum。使用HeapTupleGetDatum(HeapTuple tuple)

    把一个 HeapTuple 转换为一个有效的 Datum。如果你想只返回一行,那么这个 Datum 可以用于直接返回,或者是它可以用作在一个返回集合的函数里的当前返回值。

    例子在下面给出。

    从 C 语言函数里返回集合

    还有一个特殊的 API 用于提供从 C 语言函数中返回集合(多行)的支持。一个返回集合的函数必须遵循版本-1的调用方式。同样,源代码必须包含 funcapi.h,就像上面说的那样。

    一个返回集合的函数(SRF)通常为它返回的每个项都调用一次。因此 SRF 必须保存足够的状态用于记住它正在做的事情以及在每次调用的时候返回下一个项。PostgreSQL 提供了 FuncCallContext 结构用于帮助控制这个过程。fcinfo->flinfo->fn_extra 用于保存一个跨越多次调用的指向 FuncCallContext 的指针。typedef struct

    {

    /*

    * 我们前面已经被调用的次数

    *

    * 初始的时候,call_cntr 被 SRF_FIRSTCALL_INIT() 置为里 0,并且

    * 每次你调用 SRF_RETURN_NEXT() 的时候都递增

    */

    uint32 call_cntr;

    /*

    * 可选的最大调用数量

    *

    * 这里的 max_calls 只是为了方便,设置它也是可选的

    * 如果没有设置,你必须提供可选的方法来知道函数何时结束

    *

    */

    uint32 max_calls;

    /*

    * 指向结果槽位的可选指针

    *

    * 这个数据类型已经过时,只用于向下兼容。也就是那些使用

    * 废弃的 TupleDescGetSlot() 的用户定义 SRF

    */

    TupleTableSlot *slot;

    /*

    * 可选的指向用户提供的杂项环境信息的指针

    *

    * user_fctx 用做一个指向你自己的结构的指针,包含任意提供给你的函数的调用间的环境信息

    *

    */

    void *user_fctx;

    /*

    * 可选的指向包含属性类型输入元信息的结构数组的指针

    *

    *

    * attinmeta 用于在返回元组的时候(也就是说返回复合数据类型)

    * 在只返回基本(也就是标量)数据类型的时候并不需要。

    * 只有在你准备用 BuildTupleFromCStrings() 创建返回元组的时候才需要它

    *

    */

    AttInMetadata *attinmeta;

    /*

    * 用于必须在多次调用间存活的结构的内存环境

    *

    * multi_call_memory_ctx 是由 SRF_FIRSTCALL_INIT() 为你设置的,并且由

    * SRF_RETURN_DONE() 用于清理。它是用于存放任何需要跨越多次调用 SRF 之间重复使用的内存

    *

    *

    */

    MemoryContext multi_call_memory_ctx;

    /*

    * 可选的指针,指向包含元组描述的结构

    *

    * tuple_desc 用于返回元组(也就是说复合数据类型)

    * 并且只是在你想使用 heap_form_tuple() 而不是 BuildTupleFromCStrings() 制作元组的

    * 时候需要。请注意这里存储的 TupleDesc 指针通常应该先用heap_form_tuple()

    * BlessTupleDesc() 处理。

    */

    TupleDesc tuple_desc;

    } FuncCallContext;

    一个 SRF 使用好几个函数和宏来把操作 FuncCallContext 结构的事情自动化(我们可以通过 fn_extra 找到它)。用SRF_IS_FIRSTCALL()

    来判断你的函数是第一次调用还是后继的调用。(只有)在第一次调用的时候,用SRF_FIRSTCALL_INIT()

    初始化 FuncCallContext。在每次函数调用时(包括第一次),使用SRF_PERCALL_SETUP()

    为使用 FuncCallContext 做恰当的设置以及清理任何前面的回合里面剩下的已返回的数据。

    如果你的函数有数据要返回,使用SRF_RETURN_NEXT(funcctx, result)

    返回给调用者。(result 必须是个 Datum,要么是单个值,要么是象前面介绍的那样准备的元组。)最后,如果你的函数结束了数据返回,使用SRF_RETURN_DONE(funcctx)

    清理并结束SRF。

    在 SRF 被调用的时候的内存环境是一个临时的环境,在调用之间将会被清理掉。这意味着你不需要 pfree 所有你 palloc 的东西;它会自动消失的。不过,如果你想分配任何跨越调用存在的数据结构,那你就需要把它们放在其它什么地方。被 multi_call_memory_ctx 引用的环境适合用于保存那些需要直到 SRF 结束前都存活的数据。在大多数情况下,这意味着你在做第一次调用的设置的时候应该切换到 multi_call_memory_ctx。

    一个完整的伪代码例子看起来像下面这样:Datum

    my_Set_Returning_Function(PG_FUNCTION_ARGS)

    {

    FuncCallContext *funcctx;

    Datum result;

    MemoryContext oldcontext;

    ''还有更多的声明''

    if (SRF_IS_FIRSTCALL())

    {

    funcctx = SRF_FIRSTCALL_INIT();

    oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);

    /* 这里放出现一次的设置代码:*/

    用户定义代码

    if 返回复合

    制作 TupleDesc,以及可能还有 AttInMetadata

    endif 返回复合

    用户定义代码

    MemoryContextSwitchTo(oldcontext);

    }

    /* 每次都执行的设置代码在这里出现:*/

    用户定义代码

    funcctx = SRF_PERCALL_SETUP();

    用户定义代码

    /* 这里只是用来测试我们是否完成的一个方法:*/

    if (funcctx->call_cntr < funcctx->max_calls)

    {

    /* 这里我们想返回另外一个条目:*/

    用户代码

    获取结果 Datum

    SRF_RETURN_NEXT(funcctx, result);

    }

    else

    {

    /* 这里我们完成返回条目的工作了,只需要清理就OK了:*/

    用户代码

    SRF_RETURN_DONE(funcctx);

    }

    }

    一个返回复合类型的完整 SRF 例子看起来象这样:PG_FUNCTION_INFO_V1(retcomposite);

    Datum

    retcomposite(PG_FUNCTION_ARGS)

    {

    FuncCallContext *funcctx;

    int call_cntr;

    int max_calls;

    TupleDesc tupdesc;

    AttInMetadata *attinmeta;

    /* 只是在第一次调用函数的时候干的事情 */

    if (SRF_IS_FIRSTCALL())

    {

    MemoryContext oldcontext;

    /* 创建一个函数环境,用于在调用间保持住 */

    funcctx = SRF_FIRSTCALL_INIT();

    /* 切换到适合多次函数调用的内存环境 */

    oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);

    /* 要返回的元组总数 */

    funcctx->max_calls = PG_GETARG_UINT32(0);

    /*

    * 为我们的结果类型制作一个元组描述

    */

    if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)

    ereport(ERROR,

    (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),

    errmsg("function returning record called in context "

    "that cannot accept type record")));

    /*

    * 生成稍后从裸 C 字串生成元组的属性元数据

    *

    */

    attinmeta = TupleDescGetAttInMetadata(tupdesc);

    funcctx->attinmeta = attinmeta;

    MemoryContextSwitchTo(oldcontext);

    }

    /* 每次函数调用都要做的事情 */

    funcctx = SRF_PERCALL_SETUP();

    call_cntr = funcctx->call_cntr;

    max_calls = funcctx->max_calls;

    attinmeta = funcctx->attinmeta;

    if (call_cntr < max_calls) /* 在还有需要发送的东西时继续处理 */

    {

    char **values;

    HeapTuple tuple;

    Datum result;

    /*

    * 准备一个数值数组用于版本的返回元组。

    * 它应该是一个 C 字串数组,稍后可以被合适的类型输入函数处理。

    *

    */

    values = (char **) palloc(3 * sizeof(char *));

    values[0] = (char *) palloc(16 * sizeof(char));

    values[1] = (char *) palloc(16 * sizeof(char));

    values[2] = (char *) palloc(16 * sizeof(char));

    snprintf(values[0], 16, "%d", 1 * PG_GETARG_INT32(1));

    snprintf(values[1], 16, "%d", 2 * PG_GETARG_INT32(1));

    snprintf(values[2], 16, "%d", 3 * PG_GETARG_INT32(1));

    /* 制作一个元组 */

    tuple = BuildTupleFromCStrings(attinmeta, values);

    /* 把元组做成 datum */

    result = HeapTupleGetDatum(tuple);

    /* 清理(这些实际上并非必要) */

    pfree(values[0]);

    pfree(values[1]);

    pfree(values[2]);

    pfree(values);

    SRF_RETURN_NEXT(funcctx, result);

    }

    else /* 在没有数据残留的时候干的事情 */

    {

    SRF_RETURN_DONE(funcctx);

    }

    }

    在 SQL 里声明这个函数的一个方法是:CREATE TYPE __retcomposite AS (f1 integer, f2 integer, f3 integer);

    CREATE OR REPLACE FUNCTION retcomposite(integer, integer)

    RETURNS SETOF __retcomposite

    AS 'filename', 'retcomposite'

    LANGUAGE C IMMUTABLE STRICT;

    另外一个方法是使用 OUT 参数:CREATE OR REPLACE FUNCTION retcomposite(IN integer, IN integer,

    OUT f1 integer, OUT f2 integer, OUT f3 integer)

    RETURNS SETOF record

    AS 'filename', 'retcomposite'

    LANGUAGE C IMMUTABLE STRICT;

    请注意在这个方法里,函数的输出类型实际上是匿名的 record 类型。

    参阅源码发布包里的 contrib/tablefunc 获取更多有关返回集合的函数的例子。

    多态参数和返回类型

    C 语言函数可以声明为接受和返回多态的类型 anyelement,anyarray,anynonarray 和 anyenum。参阅 Section 34.2.5 获取有关多态函数的更详细的解释。如果函数参数或者返回类型定义为多态类型,那么函数的作者就无法预先知道他将收到的参数,以及需要返回的数据。在 fmgr.h 里有两个过程,可以让版本-1的 C 函数知道它的参数的确切数据类型以及它需要返回的数据类型。这两个过程叫 get_fn_expr_rettype(FmgrInfo *flinfo) 和 get_fn_expr_argtype(FmgrInfo *flinfo, int argnum)。它们返回结果或者参数的类型 OID,如果这些信息不可获取,则返回 InvalidOid。结构 flinfo 通常是以 fcinfo->flinfo 进行访问的。参数 argnum 是以 0 为基的。get_call_result_type 也可以用做 get_fn_expr_rettype 替换。

    比如,假设我们想写一个函数接受任意类型的一个元素,并且返回该类型的一个一维数组:PG_FUNCTION_INFO_V1(make_array);

    Datum

    make_array(PG_FUNCTION_ARGS)

    {

    ArrayType *result;

    Oid element_type = get_fn_expr_argtype(fcinfo->flinfo, 0);

    Datum element;

    int16 typlen;

    bool typbyval;

    char typalign;

    int ndims;

    int dims[MAXDIM];

    int lbs[MAXDIM];

    if (!OidIsValid(element_type))

    elog(ERROR, "could not determine data type of input");

    /* 获取提供的元素 */

    element = PG_GETARG_DATUM(0);

    /* 我们的维数是 1 */

    ndims = 1;

    /* 有一个元素 */

    dims[0] = 1;

    /* 数组下界是 1*/

    lbs[0] = 1;

    /* 获取有关元素类型需要的信息 */

    get_typlenbyvalalign(element_type, &typlen, &typbyval, &typalign);

    /* 然后制作数组 */

    result = construct_md_array(&element, ndims, dims, lbs,

    element_type, typlen, typbyval, typalign);

    PG_RETURN_ARRAYTYPE_P(result);

    }

    下面的命令用 SQL 声明函数 make_array:CREATE FUNCTION make_array(anyelement) RETURNS anyarray

    AS 'DIRECTORY/funcs', 'make_array'

    LANGUAGE C STRICT;

    请注意使用 STRICT;这一点非常重要,因为代码没有认真测试空输入。

    共享内存和 LWLocks

    附加模块可以在服务器启动的时候保留 LWLocks 和分配一篇共享内存。附加模块的共享库必须通过在 shared_preload_libraries 里面声明来预装载。共享内存是通过在你的 _PG_init 函数里面调用:void RequestAddinShmemSpace(int size)

    来保留的。

    LWLocks 是通过在 _PG_init 里调用:void RequestAddinLWLocks(int n)

    来保留的。

    为了避免可能的冲突条件,在访问每个后端并且初始化其分配的共享内存的时候,每个后端都应该使用 LWLock AddinShmemInitLock,如下所示:static mystruct *ptr = NULL;

    if (!ptr)

    {

    bool found;

    LWLockAcquire(AddinShmemInitLock, LW_EXCLUSIVE);

    ptr = ShmemInitStruct("my struct name", size, &found);

    if (!ptr)

    elog(ERROR, "out of shared memory");

    if (!found)

    {

    初始化 shmem 区域的内容

    使用下面一行请求任何需要的 LWLocks:

    ptr->mylockid = LWLockAssign();

    }

    LWLockRelease(AddinShmemInitLock);

    }

    展开全文
  • 内部变量是指定义在函数内部函数参数及变量。外部变量定义在函数之外,因此可以在许多函数中使用。由于C语言不允许在一个函数中定义其他函数,因此函数本身只能是“外部的”。  由于C语言代码是以文件为单位来...
  • 单片机 C 语言模块化编程下面让我们揭开模块化神秘面纱,一窥其真面目。C 语言源文件 *.c提到 C 语言源文件,大家都不会陌生。因为我们平常写的程序代码几乎都在这个 XX.C 文件里面。编译器也是以此文件来进行编译并...
  • 首先A、D、S、L、C、F、I 他们都在 /THINKPHP/Common/functions...L() 获取语言定义 C() 获取配置值 F()快速文件数据读取和保存 针对简单类型数据 字符串、数组 I() 快速创建一个对象实例 1.A快速创建Ac
  • D、F、S、C、L、A、I 等函数都在functions.php这个文件 下面我分别说明一下他们的功能 D() 加载Model类 M() 加载Model类 A() 加载Action类 L() 获取语言定义 C() 获取配置值 用法就是 C("这里填写在...
  • Thinkphp 内部函数 ADSLCFI 转载

    千次阅读 2011-05-09 10:40:00
    Thinkphp 内部函数 ADSLCFI ThinkPHP为一些常用的操作定义了快捷方法,这些方法具有单字母的方法名,具有比较容易记忆的特点。非常有意思的是,这些快捷方法的字母包含了ADSL字母,所以我们称之为ADSL方法 ---引自...
  • php中文网最新课程每日17点...通过本文,你可以窥探下● gdb 的简单使用● gdb gui 模式初探● 看看平时写的 PHP 代码在 C 语言里的样子● 对使用 gdb 调试 php 代码有个初步了解● 对了,文末有一些截图,不要错过...
  • 一、函数总结说明1.1不能在非函数的代码块中声明函数不要在代码块中定义一个函数,这样造成假的情况下不执行1.2 name属性和length属性1.3变量和函数的提升函数的提升1.4 超一等公民 在很多传统语言C/C++/Java/C#等...
  • 函数 所有函数的参值都是按值传递
  • 2、函数存在,进入调用函数储存栈(先入后出),等待此函数内部函数调用完成后,在继续执行此函数。 变量提升 1、初始化arguments变量 2、内部变量提升和函数提升 2.1、函数名和变量名重名时候,函数名优先级大于...
  • 学习过C/C++编程语言函数的人都知道,C/C++语言函数有“形参”与“实参”之分: “形参”即“形式参数”的简称,在函数没有被调用的时候,“形参”是没有实际的值的。“实参”即“实际参数”的简称,在函数被调用...
  • 普通变量是直接传值show(int a,int b)指针变量传递地址show(int *a,int *b)引用变量是隐式传地址 1:int...void show(int &x),实际传递的是x的地址p,但是函数内部使用x时会自动变成*p void f(int &x) {x++}; 会变成 
  • 学习过C/C++编程语言函数的人都知道,C/C++语言函数有“形参”与“实参”之分: “形参”即“形式参数”的简称,在函数没有被调用的时候,“形参”是没有实际的值的。“实参”即“实际参数”的简称,在函数被调用...
  • 在前面的部分中,ircmaxell 博客 解释了在哪里可以找到PHP源...在本文中,我们将讨论PHP代码库中的内部函数的定义,以及理解它们的含义。如何找到函数定义首先,让我们试着找到strpos函数的定义。 首先要做的是,在P
  • 前言:继续C基础系列。 1.静态变量 没什么可说的,概念性东西。 2.寄存器变量  寄存器存在于CPU内部,运算速度非常快, 因为内存中的数据必须载入寄存器才能计算。如果直接定义一个变量为...
  • thinkphp 内部函数 Submitted by mjxhehe on 2010, January 20, 3:49 PM. PHP D、F、S、C、L、A、I 他们都在functions.php这个文件家 下面我分别说明一下他们的功能 D() 加载Model类 M() 加载...
  • 学习过C/C++编程语言函数的人都知道,C/C++语言函数有“形参”与“实参”之分: “形参”即“形式参数”的简称,在函数没有被调用的时候,“形参”是没有实际的值的。 “实参”即“实际参数”的简称,在函数被...
  • 摘自:http://bbs.blueidea.com/viewthread.php?tid=2901311D、F、S、C、L、A、I 他们都在functions.php这个文件家下面我分别说明一下他们的功能D() 加载Model类A() 加载Action类L() 获取语言定义C() 获取...
  • (1)很多从其他语言(例如C++)转到objective c的初学者,往往会问到一个问题,如何定义类的私有函数?这里的“私有函数”指的是,某个函数只能在类的内部使用,不能在类的外部,或者派生类内部使用。事实上,...
  • 测试目录: 1.普通函数指针指向普通函数 ...5. 类内部函数指针 指向成员函数 (类似于第2条) 6. 类内部函数指针 指向普通函数 直接上代码: #include #include #include #include using namespa
  • c程序函数返回值

    2017-02-17 15:49:21
    在《C程序设计语言(K&R)》A.10.1节中是这样说的,函数可返回算数类型(也就是一个表达式),结构,联合,指针或void类型的值,但是不能返回函数或数组类型。 注意,函数是可以返回结构体的,并且是函数内部定义的...
  • 第一点,字符指针与函数:字符串常量是一个字符数组,如“I am a string”,在字符串的内部表示中,字符数组以空字符’\0’结尾,所以,程序可以通过检查空字符找到字符数组的结尾。 字符串常量最常见的用法是作为...
  • 函数调用(Function Calls)内部函数调用(Internal Function Calls)当前合约的函数可以直接内部(Internal)调用,也可以递归地调用,比如这个古怪的例子:pragma solidity ^0.4.16; contract C { function g(uint a) ...
  • 这章举例实现了一个内存管理函数malloc 将系统以来的部分和非系统依赖的部分进行分离 malloc内部管理的内存区块实际上是一个环链表,用一个全局指针纪录上一次操作的节点,用户遍历时判断是否已经遍历一周。 每个...
  • 函数内部临时变量

    千次阅读 2013-07-06 21:17:38
    这是我在回复CSDN的C版区一个问临时变量问题的回复内容。以前也看到很多类似的问题,这次写了几句简单的代码,测试后算作一个总结吧! #include 2 static int num = 10;... //嵌入汇编语言,咱

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 1,692
精华内容 676
关键字:

c语言内部函数

c语言 订阅