一度誤った投稿をしてしまいそれがGoogle Reader Cacheに保存されてしまうと削除は難しいようだ(参照 Google Reader FAQ: Deleted posts in my blog’s feed)。仮にその記事を削除したとしても同じ。というわけでいきなりこの記事のタイトル「Google Reader CacheからItemを削除する方法」はできないということになる。ただ、FAQでコメントされているように、どうしてもCacheから削除したい場合、削除はできないがアップデートはできるという特性を活かしダミー記事Itemでリプレースするという方法がある。Itemはguidをキーとして認識する。よって同一guidのダミーItemを用意しそいつでリプレースをかけることで何とか取り繕うことができる。
まずは誤って投稿した記事ItemがCacheされてしまった様子。タイトル・コンテンツともに空にもかかわらずキャッシュされてしまっている。仮にこのItemのguidがhttp://yk55.com/blog/?p=200とする。この誤ってキャッシュされてしまったItemを別の何かでリプレースするためのダミー記事を用意する。もちろんその記事には同一guidを指定してやる。
<item>
<title>[dummy] Google Reader CacheからItemを削除する方法</title>
<link>http://yk55.com/blog/2010/02/27/google-reader-cache-item-removal/</link>
<comments>http://yk55.com/blog/2010/02/27/google-reader-cache-item-removal/#comments</comments>
<pubDate>Sat, 27 Feb 2010 14:55:05 +0000</pubDate>
<dc:creator>yoichi</dc:creator>
<category><![CDATA[Uncategorized]]></category>
<category</category>
<guid isPermaLink="false">http://yk55.com/blog/?p=200</guid>
<description>~略~</description>
<content:encoded>~略~</content:encoded>
<wfw:commentRss>http://yk55.com/blog/2010/02/27/google-reader-cache-item-removal/feed/</wfw:commentRss>
<slash:comments>0</slash:comments>
</item>
これを追加したRSSフィードを公開することで時間がたてばフィードがクローリングされ、さきほどの間違ったItemがダミーItemでリプレースされる。以下が見事にリプレースされた結果。
あまりにもべたなやり方だけど、どうしてもなんとかしたい場合にはこの方法で。おわり。
Posted in: google, random
Tags: feed, google, xml
たまにc++コードのコンパイル時にエラー文言でvtableというキーワードを見たことはないだろうか?Polymorphismという有名なワードに対しこのvtableはあまりにも目立たない存在だ。とはいえPolymorphismを実現するためになくてはならない(最)重要なポジションを占めているのがvtable。別にvtableを理解しなくともPolymorphismの理解はできる。 ただし、骨太になりたいのならばPolymorphismを実現するためにどのようにvtableが使われているのかを理解しておくべきである。
UPCASTING
Virtual関数との比較のためにVirtual関数を持たないクラス継承を説明する。以下のようにvirtual関数を持たないSuperClassをSubClassが継承する。 SuperClassを継承したSubClassの参照をSuperClassポインタ変数に入れた場合は、子クラスへの参照がUPCASTされ親クラスへの参照として扱われる。実行結果はSuperClassへの参照へとUPCASTされるのでSuperClassの関数の内容が表示される。
#include <iostream>
using std
::cout;
using std
::endl;
class SuperClass
{
public
:
void func
() { cout < "SuperClass::func()" < endl
; }
};
class SubClass
: public SuperClass
{
public
:
void func
() { cout < "SubClass::func()" < endl
; }
};
int main
(int argc
, char **argv
) {
SubClass sub
;
SuperClass
*super
=&sub
;
super
->func
();
return 0;
}
[実行結果]
SuperClass::func()
VIRTUAL FUNCTION, VTABLE, and VPOINTER
次にVirtual関数の例を説明する。さきほどの例ではUPCASTによりSubClassクラスではなくSuperClassクラスのfunc関数がcallされた。ここではSuperClassの関数にvirtualをつけた場合のfunc関数の振る舞いを確かめてみる。virtual関数func1、func2をもつSuperClassを継承した3つのSubClassを用意する。これら3つはそれぞれfunc1、func2両方とも、func1のみ、func2のみをoverrideしている。 実行結果はSubClassでvirtual関数をoverrideした場合はその内容が、そうでない場合はSuperClassの内容が表示される。
#include <iostream>
using std
::cout;
using std
::endl;
class SuperClass
{
public
:
virtual
void funcA
() { cout << "SuperClass::funcA()" << endl
; };
virtual
void funcB
() { cout << "SuperClass::funcB()" << endl
; };
};
class SubClass1
: public SuperClass
{
public
:
void funcA
() { cout << "SubClass1::funcA()" << endl
; }
void funcB
() { cout << "SubClass1::funcB()" << endl
; }
};
class SubClass2
: public SuperClass
{
public
:
void funcA
() { cout << "SubClass2::funcA()" << endl
; }
};
class SubClass3
: public SuperClass
{
public
:
void funcB
() { cout << "SubClass2::funcB()" << endl
; }
};
int main
(int argc
, char **argv
) {
SubClass1 sub1
; SubClass2 sub2
; SubClass3 sub3
;
cout << "***************SubClass1***************" << endl
;
SuperClass
*super
=&sub1
;
super
->funcA
(); super
->funcB
();
cout << "***************SubClass2***************" << endl
;
super
=&sub2
;
super
->funcA
(); super
->funcB
();
cout << "***************SubClass3***************" << endl
;
super
=&sub3
;
super
->funcA
();super
->funcB
();
return 0;
}
[実行結果]
***************SubClass1***************
SubClass1::funcA()
SubClass1::funcB()
***************SubClass2***************
SubClass2::funcA()
SuperClass::funcB()
***************SubClass3***************
SuperClass::funcA()
SubClass2::funcB()
virtualな関数の場合はコンパイラは型ではなくてオブジェクトのポインタを見ていて実行時にどのfuncが実行されるべきかを判断する。非virtual関数の時と違いコンパイラーはコンパイル時にはどのfuncが呼ばれるのか判別できない。この実行時にどのfuncが呼ばれるのか決定することをDynamic Bindingと呼ぶ。そしてこのDynamic BindingはVirtual Function table(Vtable)と呼ばれるメカニズムによって実現される。ようやく本題。
Vtableとはvirtual関数を持っているクラスや親クラスで定義されているvirtual関数をoverrideしたクラスに対してコンパイラーが作成する(その名のとおり)仮想テーブルである。コンパイラーはvirtual関数を持っている/virtaul関数をoverrideしているクラスにのみクラスごとのVtableを作成してその中にbindすべき関数ポインターを持っている。
またこのVtableを指すポインタのことをvpointerと呼ぶ。コンパイラはVtableを持っているクラスに対してvpointerを隠しメンバー変数として追加する。さらにコンストラクタにそのvpointer変数の初期化を行うコードを追加する。よってオブジェクトが作成されるとき隠しメンバー変数vpointerは対応するVtableアドレスで初期化され、実行時の実行関数の決定は内部でvpointerを通じてVtableをlookupすることで実現される。 (参照: wikipedia:vtable implementation)
Vtableに作成される関数ポインターは、そのクラスで持っているvirtual関数のポインター、親クラスで定義されているvirtaul関数をoverrideした関数のポインター、また親クラスでvirtual定義されている関数をoverrideしない場合はその親のvirtual関数ポインタが含まれることになる。上記サンプルにあるSuperClass, SubClass[1-3]に対するオブジェクトとvpointer、vtableとそのvtableに含まれる関数ポインターの関係を図にすると以下のようになる。

上図を元にサンプル中のSubClass1::funcAのDynamic Bindingイメージを式化してみると次のような感じになる・ vptr1はSubClass1のvpointerとする。 あくまでイメージであり(vptr->)は実際は見えません。
SubClass1 sub1;
SuperClass *super =&sub1;
super->vptr->funcA(); // SubClass1::funcA(),
g++ -fdump-class-hierarchのダンプ結果
最後にg++の-fdump-class-hierarchオプションによるVtableのダンプ結果を見てみる。上記サンプルファイルをvtable.cppとして次のようにコンパイルを行う。 (参考: Options for Debugging Your Program or GCC)
g++ -fdump-class-hierarchy vtable.cc -o vtable
これでコンパイルが終わりvtable実行ファイルができあがる。また同一ディレクトリにvtable.cpp.002t.classという名前のファイルが出来上がる。このファイルにVtableのダンプ結果が出力されている。各クラスのVtable中を見るといまいち意味のわからないものはあるが上図のとおりの関数が含まれており、また各クラスにはvptrを見つけることができる。
Vtable for SubClass1
SubClass1::_ZTV9SubClass1: 4u entries
0 (int (*)(...))0
4 (int (*)(...))(& _ZTI9SubClass1)
8 SubClass1::funcA
12 SubClass1::funcB
Class SubClass1
size=4 align=4
base size=4 base align=4
SubClass1 (0xb7254a80) 0 nearly-empty
vptr=((& SubClass1::_ZTV9SubClass1) + 8u)
SuperClass (0xb707c1e0) 0 nearly-empty
primary-for SubClass1 (0xb7254a80)
Vtable for SubClass2
SubClass2::_ZTV9SubClass2: 4u entries
0 (int (*)(...))0
4 (int (*)(...))(& _ZTI9SubClass2)
8 SubClass2::funcA
12 SuperClass::funcB
Class SubClass2
size=4 align=4
base size=4 base align=4
SubClass2 (0xb7254b80) 0 nearly-empty
vptr=((& SubClass2::_ZTV9SubClass2) + 8u)
SuperClass (0xb707c3c0) 0 nearly-empty
primary-for SubClass2 (0xb7254b80)
Vtable for SubClass3
SubClass3::_ZTV9SubClass3: 4u entries
0 (int (*)(...))0
4 (int (*)(...))(& _ZTI9SubClass3)
8 SuperClass::funcA
12 SubClass3::funcB
Class SubClass3
size=4 align=4
base size=4 base align=4
SubClass3 (0xb7254c40) 0 nearly-empty
vptr=((& SubClass3::_ZTV9SubClass3) + 8u)
SuperClass (0xb707c528) 0 nearly-empty
primary-for SubClass3 (0xb7254c40)
おわり
Posted in: cplusplus
Tags: cplusplus, gcc, polymorphism, vpointer, vtable
新ブログに移行してから約二十日が過ぎ、ようやくこれまで旧ブログのフィードを指していたFeedburnerのフィードアドレスを新ブログのフィードに付け直した。公開アドレスはそのままで実体だけが入れ替っただけなのでこれまでの公開アドレスでRSS登録していただいた方には特に設定変更することなく新しい方のがフィードされてくるようになります。
公開用フィードアドレス (これをRSSリーダーに登録しておけばOK!)
http://feed.yk55.com/yokawasa/syndication
※上記はFeedburnerマイ・ブランド設定されたもので実際は以下のアドレスへのCNAMEです。
http://feeds.feedburner.com/yokawasa/syndication
マイ ブランドの概要とFAQ
公開アドレスへの紐付け変更内容
(以前)
Original Feed: http://blog.yk55.com/rss/atom.xml
Feed Address: http://feeds.feedburner.com/yokawasa/syndication
↓
(以後)
Original Feed: http://yk55.com/blog/feed/
Feed Address: http://feeds.feedburner.com/yokawasa/syndication
というわけで上記のようにfeedburnerのフィードアドレス設定をしてそれで公開しておくと今回のようなドメイン、パスが変更になった場合にフィード登録者に手を煩わせることがないので便利です。
Posted in: random
Tags: feedburner, google, rss
readv、writevは複数バッファをまとめて読み書きするシステムコールです。 writevはiovec 構造体の配列に書き込みたいものを突っ込んでまとめてその複数バッファーを書き込み、readvは指定した個数分の複数バッファーをiovec 構造体の配列に格納します。例えばwritevによる複数バッファの書き込みはこんな感じです。
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <sys/uio.h>
int
main
( int argc
, char** argv
) {
int fd
= 0;
ssize_t written
;
struct iovec iov
[3
];
char *buffer0
, *buffer1
, *buffer2
;
char *file
= argv
[1
];
fd
= open
( file
, O_RDWR
|O_CREAT
|O_EXCL
, 0666
);
if ( fd
< 0
) {
printf("cannot open file:%s\n", file
);
return -1;
}
buffer0
= "buffer0 string";
buffer1
= "buffer1 string";
buffer2
= "buffer2 string";
iov
[0
].
iov_base = buffer0
;
iov
[0
].
iov_len = strlen
(buffer0
);
iov
[1
].
iov_base = buffer1
;
iov
[1
].
iov_len = strlen
(buffer1
);
iov
[2
].
iov_base = buffer2
;
iov
[2
].
iov_len = strlen
(buffer2
);
written
= writev
(fd
, iov
, 3
);
close
(fd
);
return 0;
}
※iovec 構造体の定義
/* Structure for scatter/gather I/O. */
struct iovec
{
void *iov_base; /* Pointer to data. */
size_t iov_len; /* Length of data. */
};
manによると基本的にはこのシステムコールの特徴は次の2つです。
- 1. readv、writevともに複数のバッファにデータを読み込む点を除いてそれぞれread(2)、write(2)と全く同様の動作を行う。
- 2. readvと writevともにatomic。writevによるデータ書き込み中に他のスレッド、プロセスのwrite による割り込みが入らない。readvも同様にファイルから連続するデータブロックが読み出すことを保証。
本当にこのシステムコールが上記のような実装になっているのか確かめるために実際にソースコードを覗いてみます。readv、writevでは内部で次のdo_readv_writev関数がコールされます。
do_readv_writev @ linux-2.6.12.1/fs/read_write.c
static ssize_t do_readv_writev(int type, struct file *file,
const struct iovec __user * uvector,
unsigned long nr_segs, loff_t *pos)
{
...
ret = rw_verify_area(type, file, pos, tot_len);
if (ret)
goto out;
fnv = NULL;
if (type == READ) {
fn = file->f_op->read;
fnv = file->f_op->readv;
} else {
fn = (io_fn_t)file->f_op->write;
fnv = file->f_op->writev;
}
if (fnv) {
ret = fnv(file, iov, nr_segs, pos);
goto out;
}
/* Do it by hand, with file-ops */
ret = 0;
vector = iov;
while (nr_segs > 0) {
void __user * base;
size_t len;
ssize_t nr;
base = vector->iov_base;
len = vector->iov_len;
vector++;
nr_segs--;
nr = fn(file, base, len, pos);
if (nr < 0) {
if (!ret) ret = nr;
break;
}
ret += nr;
if (nr != len)
break;
}
...
}
後半のwhileブロックを見るとたしかにreadv、writevのときそれぞれ内部でread、writeがループで繰り返されています。 想像を超えたシンプルさでした。次にatomicを保障している箇所ですがREADかWRITEのtypeチェックを行う前のrw_verify_areaがそれっぽい。ということでrw_verify_areaを覗いてみます。
rw_verify_area @ linux-2.6.12.1/fs/read_write.c
int rw_verify_area(int read_write, struct file *file, loff_t *ppos, size_t count)
{
struct inode *inode;
loff_t pos;
if (unlikely(count > file->f_maxcount))
goto Einval;
pos = *ppos;
if (unlikely((pos < 0) || (loff_t) (pos + count) < 0))
goto Einval;
inode = file->f_dentry->d_inode;
if (inode->i_flock && MANDATORY_LOCK(inode))
return locks_mandatory_area(read_write == READ ? FLOCK_VERIFY_READ : FLOCK_VERIFY_WRITE, inode, file, pos, count);
return 0;
Einval:
return -EINVAL;
}
ここではファイルの読み込み・書き込みエリアのバリデーションチェックとlocks_mandatory_areaで排他チェックを行っています。念のためlocks_mandatory_areaを覗いてみます。
locks_mandatory_area @ linux-2.6.12.1/fs/locks.c
/**
* locks_mandatory_area - Check for a conflicting lock
* @read_write: %FLOCK_VERIFY_WRITE for exclusive access, %FLOCK_VERIFY_READ
* for shared
* @inode: the file to check
* @filp: how the file was opened (if it was)
* @offset: start of area to check
* @count: length of area to check
*
* Searches the inode's list of locks to find any POSIX locks which conflict.
* This function is called from rw_verify_area() and
* locks_verify_truncate().
*/
int locks_mandatory_area(int read_write, struct inode *inode,
struct file *filp, loff_t offset,
size_t count)
{
struct file_lock fl;
int error;
locks_init_lock(&fl);
fl.fl_owner = current->files;
fl.fl_pid = current->tgid;
fl.fl_file = filp;
fl.fl_flags = FL_POSIX | FL_ACCESS;
if (filp && !(filp->f_flags & O_NONBLOCK))
fl.fl_flags |= FL_SLEEP;
fl.fl_type = (read_write == FLOCK_VERIFY_WRITE) ? F_WRLCK : F_RDLCK;
fl.fl_start = offset;
fl.fl_end = offset + count - 1;
for (;;) {
error = __posix_lock_file(inode, &fl);
if (error != -EAGAIN)
break;
if (!(fl.fl_flags & FL_SLEEP))
break;
error = wait_event_interruptible(fl.fl_wait, !fl.fl_next);
if (!error) {
/*
* If we've been sleeping someone might have
* changed the permissions behind our back.
*/
if ((inode->i_mode & (S_ISGID | S_IXGRP)) == S_ISGID)
continue;
}
locks_delete_block(&fl);
break;
}
return error;
}
※ kernelソースコードlinux-2.6.12.1
以上、簡単ではありますがreadv、writevの実装でした。 このシステムコールはこれまで何度か業務で利用していておなじみな物のではありましたが特に内部の実装を気にすることなく使ってました。実際に見てみると単純な複数バッファ分のread, writeの繰り返しではありますがその処理間に排他制御がしっかりされているのでパフォーマンスはさておき安心して使えると思います。その辺の自分で実装するのは面倒ですから。
Posted in: kernel
Tags: c, kernel, linux
Javaでは普通に行われている実行時の型情報取得( Object.getClass(), Class.getName()とかinstanceOfとかね )ですが、CではできないのだからどうせC++でもできないのだろうなと思っていたら予想に反しRunTime Type Identification (RTTI)という実行時の型情報取得機能がばっちり用意されてました。 確かにdynamic_castができているわけだし、まあそういうことだよねと納得。 ということでC++で実行時クラス名の取得を試みました。
実行時型情報取得はtypeid演算子を使います。typeid演算子はtype_info型クラスの参照を返し、そのtype_infoのメンバname()より型情報の文字列が取得できます。 それでは次のコードで型情報を取得してみます。
#include <iostream>
#include <typeinfo>
using std::cout;
using std::endl;
using std::type_info;
class Parent{
public:
virtual void x(){}
};
class Child : public Parent {};
main(){
Parent* p;
p = new Parent();
const type_info & id_p = typeid(*p);
cout << id_p.name() << endl;
p = new Child();
const type_info & id_c = typeid(*p);
cout << id_c.name() << endl;
}
これをコンパイル(gcc 4.3.2)後に実行した結果は次のとおり。
ParentクラスとChildクラスそれぞれの結果は確かにそれっぽいものが得られましたがなぜかクラス名の前に文字数がくっついてます。 どうしたもんだと思ってググってみるとまさにピンポイントなMLでの議論を発見。 クラスの前の数字がゴミに見えて気づかなかったのですがこれはマングリングされた文字列でした。 というわけで問題はデマングリングで解決します。上記MLではその法についても紹介されていたのでそれを参考にデマングリングを試みます。
デマングリングにはcxxabi.hに定義されている__cxa_demangle()関数を使用します。まずはヘッダを覗いてみます。
__cxa_demangle() @ /usr/include/c++/4.1.3/cxxabi.h
#ifdef __cplusplus
namespace __cxxabiv1
{
extern "C"
{
#endif
...略...
// Demangling routines.
char*
__cxa_demangle(const char* __mangled_name, char* __output_buffer,
size_t* __length, int* __status);
#ifdef __cplusplus
}
} // namespace __cxxabiv1
#endif
...略...
// User programs should use the alias `abi'.
namespace abi = __cxxabiv1;
次にこの__cxa_demangle()を使って先のマングルされた文字列をデマングルしてみます。
#include <iostream>
#include <typeinfo>
#include <cxxabi.h>
using std::cout;
using std::endl;
using std::type_info;
class Parent{
public:
virtual void x(){} // 1. 仮想メソッド宣言
};
class Child : public Parent {};
main(){
Parent* p;
int status;
p = new Parent();
const type_info & id_p = typeid(*p);
cout << abi::__cxa_demangle(id_p.name(), 0, 0, &status) << endl;
p = new Child();
const type_info & id_c = typeid(*p);
cout << abi::__cxa_demangle(id_c.name(), 0, 0, &status) << endl;
}
デマングル版コードをコンパイル(gcc 4.3.2)後に実行してみると無事クラス名が出力。 ということで見事実行時クラス名の取得に成功しました。
おわり
Posted in: cplusplus
Tags: cplusplus, gcc, rtti