プログラミングなどなど

プログラミングに関係しそうな内容を

C言語:型に性質をもたせる?(コンパイル時の警告まで)

[増補改訂]関数プログラミング実践入門 ──簡潔で、正しいコードを書くために (WEB+DB PRESS plus)の「1.5 型に性質をもたせる HTMLの文字列エスケープ」を読んで、C言語の場合はどんな風にできるのかと思い実装してみました。おそらくコンパイル時のチェックで警告されるところまではいくのかな、と思いつつ。



ソース以下となります。

サンプルソースコンパイル

HTMLEscapedString.h

ヘッダファイルでは構造体HTMLEscapedStringのメンバを利用者に公開しないようにしています。

#ifndef __HTML_ESC_STRING__
#define __HTML_ESC_STRING__

typedef struct HTMLEscapedString HTMLEscapedString;

const HTMLEscapedString * const escape(const char *);
void putHTMLEscapedStrLn(const struct HTMLEscapedString * const);

#endif
HTMLEscapedString.c

構造体HTMLEscapedStringのメンバはヘッダファイルで利用者に公開しないようにしていますが、今回のケースでは欲しいメンバがなかったので、実際のメンバもありません。メンバを持たない構造体って今まで考えたことはなかったのですが定義できるのですね。本のサンプルではescape定義時に使用する関数escapeAmpとescapeOtherを補助として定義していますが、以下ではそれらを使用せずに処理を真似て実装しています。

#include <stdlib.h>
#include <stdio.h>

#include "HTMLEscapedString.h"

/* 型 */
struct HTMLEscapedString
{
};

/* メモリを確保して返すことにする。*/
static HTMLEscapedString * const createHTMLEscapedString(int size)
{
  return malloc(size);
}

const HTMLEscapedString * const escape(const char *src)
{
  /* 簡易的にサイズは固定とする。*/
  HTMLEscapedString *const dst = createHTMLEscapedString(128);
  char *dstp = (char *)dst;

  while(*src)
  {
    if('&' == *src)
    {
       *(dstp++) = '&';
       *(dstp++) = 'a';
       *(dstp++) = 'm';
       *(dstp++) = 'p';
    }
    else if('<' == *src)
    {
       *(dstp++) = '&';
       *(dstp++) = 'l';
       *(dstp++) = 't';
    }
    else if('>' == *src)
    {
       *(dstp++) = '&';
       *(dstp++) = 'g';
       *(dstp++) = 't';
    }
    else if('"' == *src)
    {
       *(dstp++) = '&';
       *(dstp++) = 'q';
       *(dstp++) = 'u';
       *(dstp++) = 'o';
       *(dstp++) = 't';
    }
    else
    {
      *(dstp++) = *src;
    }

    src++;
  }

  return dst;
}

void putHTMLEscapedStrLn(const struct HTMLEscapedString *str)
{
  printf("%s\n", (char *)str);
}

簡単にするためエスケープ後の文字列は適当にサイズ128で確保しています。実際にこのような作りにする場合は、変換後の文字列の領域がどれぐらい必要になるかを知るためにサイズを求める関数を別途用意するなど必要になると思います。

main.c
#include <stdio.h>
#include "HTMLEscapedString.h"

int main(void)
{
  const char *src = "Amp:&,<include>,Quote:\"";
  const HTMLEscapedString * const dst = escape(src);

  printf("%s\n", src);
  putHTMLEscapedStrLn(dst);

  /* 警告が出る */
  printf("%s\n", dst);

  return 0;
}
Makefile
all : runAll

runAll : main01 main02
	./main01
	@echo ""
	LD_LIBRARY_PATH=. ./main02

# 共有ライブラリ使用版
main02 : main.o libHTMLEscapedString.so
	gcc -Wall main.o -L . -lHTMLEscapedString -o main02

# オブジェクトファイルリンク版
main01 : main.o HTMLEscapedString.o
	gcc -Wall main.o HTMLEscapedString.o -o main01

main.o : main.c HTMLEscapedString.h
	gcc -Wall -c main.c

libHTMLEscapedString.so : HTMLEscapedString.c HTMLEscapedString.h
	gcc -Wall -shared -fPIC HTMLEscapedString.o -o libHTMLEscapedString.so

HTMLEscapedString.o : HTMLEscapedString.c HTMLEscapedString.h
	gcc -Wall -c HTMLEscapedString.c

clean :
	rm -f *.o *.so main01 main02

実行

オプジェクトファイルをリンク時に指定する方法(main01)と共有ライブラリを使用する方法で実行してみました。実行すると型が合わない旨の警告が出ています。

$ make
gcc -Wall -c main.c
main.c: In function ‘main’:
main.c:13:12: warning: format ‘%s’ expects argument of type ‘char *’, but argument 2 has type ‘const HTMLEscapedString * const {aka const struct HTMLEscapedString * const}’ [-Wformat=]
   printf("%s\n", dst);
           ~^
gcc -Wall -c HTMLEscapedString.c
gcc -Wall main.o HTMLEscapedString.o -o main01
gcc -Wall -shared -fPIC HTMLEscapedString.o -o libHTMLEscapedString.so
gcc -Wall main.o -L . -lHTMLEscapedString -o main02
./main01
Amp:&,<include>,Quote:"
Amp:&amp,&ltinclude&gt,Quote:&quot
Amp:&amp,&ltinclude&gt,Quote:&quot

LD_LIBRARY_PATH=. ./main02
Amp:&,<include>,Quote:"
Amp:&amp,&ltinclude&gt,Quote:&quot
Amp:&amp,&ltinclude&gt,Quote:&quot