質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.48%
アセンブリ言語

アセンブリ言語とは、機械語を人間にわかりやすい形で記述した低水準言語です。

C

C言語は、1972年にAT&Tベル研究所の、デニス・リッチーが主体となって作成したプログラミング言語です。 B言語の後継言語として開発されたことからC言語と命名。そのため、表記法などはB言語やALGOLに近いとされています。 Cの拡張版であるC++言語とともに、現在世界中でもっとも普及されているプログラミング言語です。

Q&A

2回答

3743閲覧

C言語とアセンブリによってprintfを自作する

mine___

総合スコア5

アセンブリ言語

アセンブリ言語とは、機械語を人間にわかりやすい形で記述した低水準言語です。

C

C言語は、1972年にAT&Tベル研究所の、デニス・リッチーが主体となって作成したプログラミング言語です。 B言語の後継言語として開発されたことからC言語と命名。そのため、表記法などはB言語やALGOLに近いとされています。 Cの拡張版であるC++言語とともに、現在世界中でもっとも普及されているプログラミング言語です。

0グッド

1クリップ

投稿2020/01/10 08:16

編集2020/01/10 08:49

C言語とアセンブリ言語を使用して、printf()関数を自作し、SPIM上でその動作を確認する応用プログラムを作成したいのですが、va_listやva_arg等のマクロを使用せずに作成する方法がわかりません。

自分でできたところまでのソースコードを記載します。

以下C言語のソースコードです。

void print_char(char c) {
char s[2];

s[0] = c;
s[1] = '\0';

print_string(s)
}

void myprintf(char *fmt, ...) {

int i, argc = 0;
char *s;

while (*fmt) {
if (*fmt == '%') {
fmt++;
argc++;
switch (*fmt){
case 'd':
// Process of %d
i = ((int) ((char *)&fmt + argc * sizeof(void *)) );
print_int(i);
break;
case 's':
// Process of %s
s = ((int) ((char *)&fmt + argc * sizeof(void *)) );
print_string(s);
break;
break;
}
}else {
// print a character as it is
print_char(*fmt);
}
fmt++;
}
}

int main() {
myprintf("I am %s, my age is %d", "○○", ○);
return 0;
}

以下アセンブリ言語のソースコードです。

.text
.align 2

_print_int:
subu $sp, $sp, 24
sw $ra, 20($sp)

li $v0, 1 # 1: print_int
syscall

lw $ra, 20($sp)
addu $sp, $sp, 24
j $ra

_print_string:
subu $sp, $sp, 24
sw $ra, 20($sp)

li $v0, 4 # 4: print_string
syscall

lw $ra, 20($sp)
addu $sp, $sp, 24
j $ra

このとき、case’u’や’o’であったり、printfの他のサブセットの実装の仕方がわかりません。
マクロを使わないのは、課題でそう指定されているからです。
同じ機能を果たすものを自分で作成するのは大丈夫です。

よろしくお願いいたします。

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

y_waiwai

2020/01/10 08:20

va_listやva_argを使わないで、というのは不可能です #同等の機能を自作するって話かな? なぜ使いたくないんでしょうか
maisumakun

2020/01/10 08:23

自分が調べたこと、あるいは「ここまではできた」というようなものはありますでしょうか?
jimbe

2020/01/10 08:26

c 言語を使ってよいのでしたら sprintf を使ってしまえば良いのではないでしょうか.
y_waiwai

2020/01/10 11:37

で、わからないといってますが、なにがわからないんでしょうか。
guest

回答2

0

va_list, va_arg の使い方がわからないのか思ったら

マクロを使わないのは、課題でそう指定されているから

なるほど、課題であるなら、

  • 関数の引数がスタックに配列のように並んでいる
  • ポインタも int も、サイズは32bit

と予想できます。それは質問者のコードからも窺えます。呼出し側がたとえば
myprintf("I am %s, my age is %d", "mine___", 20); なら

  • arr[0] : "I am %s, my age is %d"(の先頭アドレス)
  • arr[1] : "mine___"(の先頭アドレス)
  • arr[2] : 20 (という整数値)

みたいな格好になっている、即ちメモリアドレスや整数値という、異なる型のデータが同じサイズで配列のように並んでいる・・・こうだと課題にしやすいですから。

質問者の不明点はひとつ見当がついた気がします。たとえば
i = ((int) ((char*)&fmt + argc * sizeof(void*)) );

で整数値にアクセスしようとしています。もしかするとデータのあるアドレスは正しくもとめられるかもしれませんが、計算式は不適切です。が、誤りがあります。しかも一箇所に誤りが複数あると何をどう調整したらよいか迷うものです。

  • ポインタを使ったアドレス計算がおかしい
  • キャストのしかたもおかしいかも

ここで、ポインタと配列が同じように書けることを思い出しましょう。
int arr[] = { 10, 20, 30, 40 };
int *p = arr; // p は arr配列の先頭をポイントする

とした時、 *p の値は 10 であることは問題無いでしょう。その上で

  • p[1] の値も arr[1] の値も 20
  • *(p + 1) の値も 20

であることを思い出しましょう。
**p[1] にアクセスする時、 (p + 1 * sizeof(int)) とはしません。**添字に sizeof(int)) をかけると、(p + 1 * 4) 即ち *(p + 4) となり、arr[4] をアクセスすることになります。

第一の誤りは、アドレス計算がおかしいことでした。argc を引数配列のインデックスとしてるんだから、something[argc] のような格好で、まさしく配列のようにアクセスすると間違いが少ないような気がします。

キャストの使い方ですが、%d で int の引数値を読みだすなら、最初から (int*) でキャストし、argc をインデックスとして
i = ((int*) &fmt) [argc];

としたらどうでしょうか。ここを基準にして、添字アクセスを付け加えれば良いのでは。

もうひとつは、引数が文字列の先頭アドレスである %s に対応する引数の場合ですが、この引数は「"mine___"(の先頭アドレス)」のように、それ自体がポインタ変数です。引数配列の一つの要素がポインタ変数である、ということは、その配列をポイントするポインタ変数は、ポインタのポインタと見做すべきです(少なくとも文字列の先頭アドレスを読みだす時は)。

つまり、%s に対する処理には(char**)というキャストが現れるだろうな・・・これがヒントです。

SPIM上でその動作を確認

SPIM はMIPSシミュレータなのね。そして、print_int()、print_string() 等がシステムコールとして用意されている、と。質問者が書いた _print_int、_print_string が動作するかどうか、単体テストは済んでいるんですかね?

上記のアドレス計算が(計算式が不適切であっても)計算結果が合っているなら、_print_int などが正しく動作していないだけかもしれません。

投稿2020/01/11 06:54

編集2020/01/11 08:25
rubato6809

総合スコア1380

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

rubato6809

2020/01/11 08:27

読み返したら、質問者のコードでもアドレス計算それ自体は正しいかも、という気になったので、回答を修正しました。
guest

0

方針としては、引数の数と型を変えながら、関数側と呼び出し側のコンパイル結果を何パターンも調べてみて、関数側で任意個の引数を参照する方法を考えるということになるかと思います。コンパイラのソースがあるのなら、それを読むほうが早いかも。
引数を渡すのにレジスタを使わず引数はひたすらスタックに積むだけというコンパイラなら比較的簡単にできると思います。
関数側は、アセンブラで書くしかないのではと思います。

投稿2020/01/10 13:30

otn

総合スコア84557

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

まだベストアンサーが選ばれていません

会員登録して回答してみよう

アカウントをお持ちの方は

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問