diary

I like Hatena Star with a text selection.

2024-04-04

github.com

Steep で不要な RBS::Location#to_s の呼び出しを消すPRを作った。 RBSのメモリ使用量を減らす前準備。


github.com

RBSのメモリ使用量を減らすために色々やっているのだけど、計測用にベンチマークスクリプトを毎回書いているのは面倒なので、rbsリポジトリに追加するPR。


Cを書いていて、謎のセグフォが出てそこそこ悩んだ。雰囲気は次のようなコード

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

typedef struct {
  int id;
  int value;
} entry;

typedef struct {
  int len;
  int cap;
  entry entries[0];
} table;

table *table_new(int cap) {
  table *t = malloc(sizeof(table) + sizeof(entry) * cap);
  t->len = 0;
  t->cap = cap;
  return t;
}

int main(void) {
  table *t1 = table_new(1000);
  for (int i = 0; i < t1->cap; i++) {
    t1->entries[i].id = i;
    t1->entries[i].value = i * 10;
    t1->len++;
  }

  table t2 = *t1;
  printf("t1 last value: %d\n", t1->entries[t1->len-1].value);
  printf("t2 last value: %d\n", t2.entries[t2.len-1].value);

  return 0;
}

t1もt2も同じstructなので同じ値が出力されると思いきや、私の手元だとt2`を出力するところでセグフォが出てしまう。

t1 last value: 9990
zsh: segmentation fault  ./a.out

この問題に最初遭遇したときは、t2の方の書き方だけをしていたので、セグフォの原因がさっぱりだった。 その後t1の書き方をすると解決することに気がついたのだけど、「えっ->(*v).ってsyntax sugerの関係なんじゃないの??」としばらく理解ができていなかった。

これの答えが何だったかと言うと、構造体をコピーしていることが原因だった。

まず、entry entries[0] の部分が影響している。これは可変長構造体、みたいな用語(正確な用語は知らない)のテクで、メモリ使用量を抑えるのに便利。 https://satosystems.hatenablog.com/entry/20100916/1284637817 とかを参考にしていた。

これは構造体の直後のメモリ領域に配列のためのメモリ領域を確保して、構造体に配列へのポインタをもたせることなく可変長の配列を実現している。便利。

ところが、table t2 = *t1;t1 を別のローカル変数であるt2にコピーしている。ということは、このt2が保存されているアドレスはもともとmallocしてきたアドレスとは異なるということ。t2.entriest2の直後の領域を参照しようとして、セグフォになってしまっていた。