ときどきの雑記帖 RE* (新南口)
孤独の肖像
ぼくらは
— よつばとツイッター (@428and_tweet) July 15, 2023
split
splitのlimit - NullPointer’s という記事で
“a,b,c,d”.split(",", 2) は aと b,c,d に分割されるのを期待してるんですが、各言語どうでしょうか?
から
- Ruby
- JavaScript
- Go
- PHP
- Java
- Rust
- Python
といった言語での結果を挙げているのだけど
JavaScriptは異端児
もっと異端児がいた、python
とあり、その結果はさておき それぞれのマニュアル(ドキュメント)の記述はどうなっているのか 気になったので(ry
Python
まずは「異端児」のPython。
str.split(sep=None, maxsplit=- 1)
文字列を sep をデリミタ文字列として区切った単語のリストを返します。maxsplit が与えられていれば、 最大で maxsplit 回分割されます (つまり、リストは最大 maxsplit+1 要素になります)。 maxsplit が与えられないか -1 なら、分割の回数に制限はありません (可能なだけ分割されます)。
なるほど。 数の指定がいくつに分割するかではなく 何回分割を行うか。なのね。
何故そうしたかも気になるけど今回はスルー。
JavaScript
続いてもう一つの「異端児」JavaScript
String.prototype.split() - JavaScript | MDN
limit 省略可
非負の整数で、分割する数を制限します。指定された場合、文字列は separator が現れるたびに分割されますが、 limit の数の項目が配列に配置されると停止します。残りのテキストは配列に入りません。
- 制限数に達する以前に文字列の末尾に達した場合は、配列の要素が limit よりも少なくなることがあります。
- limit が 0 の場合は、分割は行われません。
これもこの仕様を採用した経緯が知りたいなあ。
Ruby
次にRuby
String#split (Ruby 3.2 リファレンスマニュアル)
第 2 引数 limit は以下のいずれかです。
limit > 0
最大 limit 個の文字列に分割する
limit == 0
分割個数制限はなしで、配列末尾の空文字列を取り除く
limit < 0
分割個数の制限はなし
Perl
元記事ではPerlには言及していないのだけど、 Rubyが参考にしていたはずなのでついでに。
Perlの組み込み関数 split の翻訳 - perldoc.jp
LIMIT が指定された正数の場合、EXPR が分割されるフィールドの最大数を 表現します; 言い換えると、 LIMIT は EXPR が分割される数より一つ大きい数です。 従って、LIMIT の値 1 は EXPR が最大 0 回分割されるということで、 最大で一つのフィールドを生成します (言い換えると、EXPR 全体の値です)。 例えば:
my @x = split(//, "abc", 1); # ("abc") my @x = split(//, "abc", 2); # ("a", "bc") my @x = split(//, "abc", 3); # ("a", "b", "c") my @x = split(//, "abc", 4); # ("a", "b", "c")
LIMIT が負数なら、非常に大きい数であるかのように扱われます; できるだけ多くの フィールドが生成されます。
Go
GoはSplitNが該当するのかな。
func Split(s, sep string) []string func SplitN(s, sep string, n int) []string
SplitN slices s into substrings separated by sep and returns a slice of the substrings between those separators.
The count determines the number of substrings to return:
n > 0: at most n substrings; the last substring will be the unsplit remainder. n == 0: the result is nil (zero substrings) n < 0: all substrings
PHP
まずsplit PHP: split - Manual を見ると
警告 この関数は PHP 5.3.0 で 非推奨 となり、 PHP 7.0.0 で 削除 されました。
この関数の代替として、これらが使えます。
- preg_split()
- explode()
- str_split()
とあったので、 PHP: explode - Manual から。
explode(string $separator, string $string, int $limit = PHP_INT_MAX): array
limit
limit に正の値が指定された場合、返される配列には 最大 limit の要素が含まれ、その最後の要素には string の残りの部分が全て含まれます。
もし limit パラメータが負の場合、 最後の -limit 個の要素を除く全ての構成要素が返されます。
limit パラメータがゼロの場合は、1 を指定したものとみなされます。
多数派(謎)と同じなので特にコメントはなく。
Java
Java。
limitパラメータは、このパターンの適用回数を制御するため、結果となる配列の長さに影響を及ぼします。 制限nがゼロより大きい場合、このパターンは最大でn - 1回まで適用され、配列の長さはn以下となります。 配列の最後のエントリには、最後に一致した区切り文字以降の入力シーケンスがすべて含まれます。 nが負の値の場合、このパターンの適用回数と配列の長さは制限されません。 nがゼロの場合、このパターンの適用回数と配列の長さは制限されませんが、 後続の空の文字列は破棄されます。
たとえば、次のパラメータが指定された場合の、文字列"boo:and:foo"の結果を示します。
正規表現 制限 結果 : 2 { "boo", "and:foo" } : 5 { "boo", "and", "foo" } : -2 { "boo", "and", "foo" } o 5 { "b", "", ":and:f", "", "" } o -2 { "b", "", ":and:f", "", "" } o 0 { "b", "", ":and:f" }
これも同じく。
Rust
Rustは…よくわからん。
- 文字列操作 with Rust - Qiita
- Ruby脳のためのRust文字列系メソッドまとめ
- SplitN in std::str - Rust
- 文字列操作 with Rust - Qiita
- [Rust]文字列(string)を空白で分割する(split by whitespace)には? | ちょげぶろぐ
- [Rust]文字列(string)を改行で分割する(split by newline)には? | ちょげぶろぐ
- 【Rust】文字列関数まとめ – Euniclus
#![feature(str_split_remainder)]
let mut split = "Mary had a little lamb".splitn(3, ' ');
assert_eq!(split.remainder(), Some("Mary had a little lamb"));
split.next();
assert_eq!(split.remainder(), Some("had a little lamb"));
split.by_ref().for_each(drop);
assert_eq!(split.remainder(), None);
イテレーター(と呼ぶのかもわからんけど)を返してるの?
それじゃあと実装を見たりしたが
impl<'a, P: Pattern<'a>> SplitN<'a, P> {
/// Returns remainder of the split string.
///
/// If the iterator is empty, returns `None`.
///
/// # Examples
///
/// ```
/// #![feature(str_split_remainder)]
/// let mut split = "Mary had a little lamb".splitn(3, ' ');
/// assert_eq!(split.remainder(), Some("Mary had a little lamb"));
/// split.next();
/// assert_eq!(split.remainder(), Some("had a little lamb"));
/// split.by_ref().for_each(drop);
/// assert_eq!(split.remainder(), None);
/// ```
#[inline]
#[unstable(feature = "str_split_remainder", issue = "77998")]
pub fn remainder(&self) -> Option<&'a str> {
self.0.remainder()
}
}
やっぱりわからん(当然)。
ところで、self
とreminder
の間にある0
ってナニ?🤔
Walter Bright
なんか発表(プレゼン)したらしい。
- Walter Bright - Programming Language Ideas That Work and Don’t Work [video] | Hacker News
- Walter Bright - “Programming Language Ideas That Work And Don’t Work” (Keynote Code Europe 2022) : r/programming
strxxx
【C言語】strncpy の使い方 という記事を見かけたのだけど
clangでstrncpyを試してみた。 すると、少々意外な結果が出た。
(略)
指定した文字数に達するまで終端(\0)で埋めているようなのである。 コピーは終端(\0)までではないのか?
とか
strncpy の歴史
昔、この標準関数はなかった。
文字列をコピーする標準関数と言えば「strcpy」だけだったのである。長年、このような文字列コピーの脆弱性が問題視されてきた。 「strcpy」の場合、上記のサンプルの「FILE」のように長さを特定し難い文字列をコピーするのは不安である。更に、 オーバーランさせてしまう不具合も少なくなかった。
いっそのことC言語の仕様から「strcpy」をなくしてしまいたかったかもしれないが、C言語の歴史は長い。 古いコードを使い続けているソフトウェアも少なくない。「strcpy」はまだ残っているかもしれないが、可能な限り「strncpy」を使うのが望ましい。
とあって、 この記事は一体どんな人が書いたのだろうかと 気になったので確認してみれば
30年以上、ソフトウェアを開発してきました。C、C++、C#、VBA、最近遊びでPython。OSはいろいろ。ほとんど組み込みソフトウェア。
……えーと。
さて、「意外な結果」についてはドキュメントを読めば解決する話で (30年選手がなぜその程度のこともしないのかと(ry)、 たとえば Man page of STRCPY に
strncpy() 関数も同様だが、 src のうち最大でも n バイトしかコピーされない点が異なる。 警告: src の最初の n バイトの中にヌルバイトがない場合、 dest に格納される文字列はヌルで終端されないことになる。
src の長さが n よりも短い場合、 strncpy() は dest に追加のヌルバイトを書き込み、全部で n バイトが書き込まれるようにする。
とあるように「そういう仕様」。 正確を期すならISOの規格から引いてくるべきなのだろうけど それはたぶんpayawllの向こうなので
The Open Group Base Specifications Issue 7, 2018 edition
を挙げておこう。
ただまあ、「なぜそういう仕様になったのか」という話であれば、 推測はできるけど説得力のある資料を提示したりはできない。 が、そうじゃないよねたぶん。
で、「strncpyの歴史」に書かれていることが にわかには信じがたいものなので(ry
K&R(ISOじゃなく、ANSIと云ってた頃よりさらに前)の時代から strncpyってあったよなあ。でもstrcpyとまったく同時かどうかは 知らん(わからん)しなあ。 ただ、少なくともstrcpy起因の脆弱性が問題にされるより前 には存在していたんじゃないかなあ。
ということで、 UNIX v7 Code search results のソースコードを見ると
v7unix/v7/usr/src/libc/gen/strncpy.c
/*
* Copy s2 to s1, truncating or null-padding to always copy n bytes
* return s1
*/
char *
strncpy(s1, s2, n)
register char *s1, *s2;
{
register i;
register char *os1;
os1 = s1;
for (i = 0; i < n; i++)
if ((*s1++ = *s2++) == '\0') {
while (++i < n)
*s1++ = '\0';
return(os1);
}
return(os1);
}
いた(笑)
ついでにマニュアルページからも。
v7unix/v7/usr/man/man3/string.3 at master · v7unix/v7unix
.TH STRING 3
.SH NAME
strcat, strncat, strcmp, strncmp, strcpy, strncpy, strlen, index, rindex \- string operations
.I Strncpy
copies exactly
.I n
characters,
truncating or null-padding
.I s2;
the target may not be null-terminated if the length
of
.I s2
is
.I n
or more.
Both return
.IR s1 .
.PP
.I Strlen
returns the number of non-null characters in
.IR s .
.PP
簡潔極まりない説明文だけど、
truncating or null-padding
とか
target may not be null-terminated
ときちんと書かれてますな。
V7の時点ですでにあるんだから、
昔、この標準関数はなかった。
というのはどこの世界線の話なんですかと言いたくなるところを ぐっとこらえて、じゃあunix v6ではと見てみたら…
strなんちゃらが丸ごとない?
起源云々はこれくらいにして話を戻すと、ひょっとして
- strlcpy
- strcpy_s
- strscpy
辺りと混同したのかな? と思わないでもないけど、にしてもねえ…
バッファオーバーフローがある程度公に文書化されたのは1972年の初めで[独自研究?]、Anderson 1972 で以下のように説明されている。
バッファオーバーフローを利用した悪意のあるエクスプロイトで最初に文書化されたのは、 1988年に書かれたMorris wormがインターネット上で増殖するのに利用していたエクスプロイトのうちの一つである。
- Morris worm - Wikipedia
- strlcpy - Wikipedia
- 1999 USENIX Annual Technical Conference, June 6-11, 1999, Monterey, California, USA
- 「strcpyをstrncpyに変更しました」が意味すること (#1183069) | +Lhaca 1.21の修正は不十分 | スラド
- STR03-C. Do not inadvertently truncate a string - SEI CERT C Coding Standard - Confluence
- Why do we assume strncpy insecure in C
- Why strncpy is not safe and how to fix it - Aticleworld
- Strncpy() is not a “safer” strcpy() | Hacker News
- The Flat Trantor Society: No, strncpy() is not a “safer” strcpy()
The description of the strcpy() and strncpy() functions is identical in the 1990, 1999, and 2011 versions of the ISO C standard – except that C99 and C11 add a footnote to the strncpy() description:
この辺の関数については以前にもちょっと触れた
glibc
とここまで書いてたら、(自分的には)びっくりするニュースが飛び込んできた。
- Strlcpy and strlcat added to glibc 2.38 | Hacker News
- Strlcpy and strlcat added to glibc 2.38 : r/hackernews
なんだってー
HNのコメントにも
It must be snowing in hell right now. :)
とかあって笑う。
理由としては、POSIXに入ったのでglibcにも入れることにした。 ということのよう。
sourceware.org Git - glibc.git/commit
Implement strlcpy and strlcat [BZ #178]
These functions are about to be added to POSIX, under Austin Group issue 986.
- sourceware.org Git - glibc.git/blob - string/strlcpy.c
- sourceware.org Git - glibc.git/blob - string/strlcat.c
にしても、strlcpyはstrlcpyでtruncateされたときの判定が面倒とかあるような…
FORTRAN Compiler on IBM 704
↑で触れたのと同じ日に
The name of a variable must not be the same as the name of any function used in the program after the terminal F of the function name has been removed. Also, if a subscripted variable has 4 or more characters in its name, the last of these must not be an F. (For the meaning of “function” and “subscripted” see Chapter 3 and the last section of this chapter.)
こちらには配列変数(subscripted variable)についての記述もあって、 名前の長さが4文字以上でなければならない。と。
…でも変数名ってMAX 6文字でなかったっけ?
ということを書いていたのだけど、
配列変数の名前が4文字以上であった場合、その最後の文字はF
であってはならない。
であって、配列変数の名前が4文字以上でなければならない
ということではない(それは関数名)。
すぐ下にあるサンプルコード
POLYF(X) = C0 + X*(C1 + X*(C2 + X*C3))
DIMENSION A(1000), B(1000)
QMAX = -1.0 E20
DO 5 I=1, 1000
QMAX = MAXF(QMAX, POLYF(A(I)) + B(I))/POLYF(A(I) - B(I)))
STOP
にも
DIMENSION A(1000), B(1000)
って長さ一文字の配列変数があるのにねえ😓
ALPHA
ALPHA
というラベルを使っているところを抜き出してみる。
>grep -e "^.\{11\}ALPHA" stateb.asm
CLS ALPHA-4,4 4F13573
CLA ALPHA-4,4 4F13590
L13130 SLW ALPHA+3,C STO -(N+2) IN ALPHA+A+3 4F13871
SLW ALPHA,C STO -C IN ALPHA+A 4F13877 store to (ALPHA+A)
L23130 SLW ALPHA,C STO -N IN ALPHA+A 4F13893
LA2000 CLS ALPHA-1,C 4F13897
L43130 SLW ALPHA,C STO -(N+2) IN ALPHA+A 4F13901
LA4000 CLS ALPHA-3,C 4F13905
SLW ALPHA-2,C STO -N IN ALPHA+A-2 4F13909
STO ALPHA-1,C STO -(N+1) IN ALPHA+A-1 4F13915
L33130 SLW ALPHA,C STO -(N+1) IN ALPHA+A 4F13944
LA3000 CLS ALPHA-2,C 4F13948
SLW ALPHA-1,C STO -N IN ALPHA+A-1 4F13952
- +1,+2がないが+0,+3はある
- *3130
- LA***
下4桁が3130なラベルを横に並べて比較してみる。 ジャンプ先もそのまま続けて書くと
L13130 SLW ALPHA+3,C L23130 SLW ALPHA,C L33130 SLW ALPHA,C L43130 SLW ALPHA,C
CLS L(0) CLS L(0) CLS L(0) CLS L(0)
STO LAMBDA+9,A STO LAMBDA+3,A STO LAMBDA+6,A STO LAMBDA+9,A
SLN 1 SLN 1 SLN 1 SLN 1
LA1000 CLS CBAR LA2000 CLS ALPHA-1,C LA3000 CLS ALPHA-2,C LA4000 CLS ALPHA-3,C
ARS 18 STO LAMBDA,A STO LAMBDA,A *
SLW ALPHA,C CLA NBAR CLS NBAR *
TXI LA1040,C,-3 TXI LA4180,A,6 ARS 18 *
LA1040 SXD ABAR,C * SLW ALPHA-1,C *
TRA LA4010 * SLW LAMBDA+2,A *
* STO LAMBDA+3,A *
* TXI LA4170,A,3 *
LA4010 STO LAMBDA,A * * LA4010 STO LAMBDA,A
CLS NBAR * * CLS NBAR
ARS 18 * * ARS 18
SLW ALPHA-2,C * * SLW ALPHA-2,C
SLW LAMBDA+2,A * * SLW LAMBDA+2,A
STO LAMBDA+3,A * * STO LAMBDA+3,A
PXD ,B * * PXD ,B
ARS 18 * * ARS 18
STO LAMBDA+5,A * * STO LAMBDA+5,A
STO ALPHA-1,C * * STO ALPHA-1,C
SSM * * SSM
STO LAMBDA+6,A * * STO LAMBDA+6,A
TXI LA4150,B,-1 * * TXI LA4150,B,-1
LA4150 CAL ADSTAR * * LA4150 CAL ADSTAR
SLW LAMBDA+4,A * * SLW LAMBDA+4,A
LA4170 PXD ,B * LA4170 PXD ,B LA4170 PXD ,B
LA4180 ARS 18 LA4180 ARS 18 LA4180 ARS 18 LA4180 ARS 18
STO LAMBDA+8,A STO LAMBDA+8,A STO LAMBDA+8,A STO LAMBDA+8,A
ORS LAMBDA+9,A ORS LAMBDA+9,A ORS LAMBDA+9,A ORS LAMBDA+9,A
CAL STRSTR CAL STRSTR CAL STRSTR CAL STRSTR
SLW LAMBDA+7,A SLW LAMBDA+7,A SLW LAMBDA+7,A SLW LAMBDA+7,A
CAL ADSPOP CAL ADSPOP CAL ADSPOP CAL ADSPOP
ORA FSBITS ORA FSBITS ORA FSBITS ORA FSBITS
ORA FNBITS ORA FNBITS ORA FNBITS ORA FNBITS
SLW LAMBDA+10,A SLW LAMBDA+10,A SLW LAMBDA+10,A SLW LAMBDA+10,A
LA4320 TXI **,A,-9 LA4320 TXI **,A,-9 LA4320 TXI **,A,-9 LA4320 TXI **,A,-9
6502
内部100MHzで動く6502らしい。
— PocketGriffon (@GriffonPocket) July 16, 2023
メモリアクセスすると遅くなるのかしらん?と思ったら、外部RAMの64KBを、内部メモリへコピーして動くっぽい。I/Oアクセスとかしない限りは100MHz動作なのか…面白い(^^)https://t.co/a3Z11IpayH
‘75
Gメン75の本部があった東京海上日動ビルも取り壊しが始まりました。#Gメン75 pic.twitter.com/0HRyb4iYPj
— 菊千代 (@kikuchiyo_0518) July 15, 2023
BBC BASIC
BASICとアセンブラがシームレスすぎてすごい。 pic.twitter.com/SvV0p3NyIm
— ちくわ帝国 (@chikuwa_empire) July 16, 2023
最後の方、ちょっと切れてるけどこんな感じか。
10 REM Sprite Demo
20 :
30 MODE 1
40 SW%=320
50 SH%=200
60 C%=64
70 :
80 DIM data ((C%*6)-1)
90 DIM code 1024
100 :
110 REM Z80 code
120 :
130 FOR I%=0 TO 3 STEP 3
140 P%=code
150 [
160 OPT I%
170 LD IX,data
180 LD BC,C%
190 LD E,0
200 :
210 LD A,23:RST &10
220 LD A,27:RST &10
230 LD A,15:RST &10
240 :
250 .loop
260 :
270 PUSH BC
280 PUSH DE
290 PUSH IX
300 LD DE,SW%:CALL movspr
310 LD DE,SH%:CALL movspr
320 POP IX
330 POP DE
340 CALL setspr
350 LD BC,6:ADD IX,BC
360 INC E
370 POP BC
380 DEC BC
390 LD A,B
400 OR C
410 JR NZ,loop
420 RET
430 :
440 .movspr
なんとなくTurbo-Cのレジスター疑変数(という名前だったような気がする)を思い出した。 自作の新言語かと思ったらそうではなく、 こういう素性のものだったらしい。
予想外の反響がw
— ちくわ帝国 (@chikuwa_empire) July 16, 2023
こちらは英国生まれのBBC BASICです。https://t.co/1ZlvuuZBnu
たまたま英国のレトロPCで興味ある機種があって、調べてたらBBC BASICやAgon Lightに辿りつきました。
またまだ知らないことがたくさんあって興味深い世界です。 https://t.co/jPK02lQL3W
これか。
One of the unique features of BBC BASIC was the inline assembler, allowing users to write assembly language programs for the 6502 and, later, the Zilog Z80, NS32016 and ARM. The assembler was fully integrated into the BASIC interpreter and shared variables with it, which could be included between the [ and ] characters, saved via *SAVE and *LOAD, and called via the CALL or USR commands. This allowed developers to write not just assembly language code, but also BASIC code to emit assembly language, making it possible to use code-generation techniques and even write simple compilers in BASIC.
へー。
ヨックモック
うわーっ YOKU MOKUの自販機だ。これで24時間いつでもシガール抱えてお詫びに行けるぞ。 pic.twitter.com/PqzwloNcWz
— お望月さん (@ubmzh) July 16, 2023
へー。ラゾーナ川崎はそこそこ行ってるけど気がつかなかった (問題の自販機の設置場所も通ってるはず)。
んが、あそこ24時間営業じゃないから、 「24時間いつでもシガール抱えてお詫び」 には行けないんじゃないかな😄
glibc
strlcpy() and strlcat() added to glibc, I never thought I'd see the day!
— Nicolas Martyanoff (@nmartyanoff) July 18, 2023
I still remember reading about Ulrich Drepper refusing to add them with insane arguments. 23 years of people copy pasting these functions everywhere just for Linux because of him.https://t.co/eKlo9qQpfs