いろいろ備忘録日記

主に .NET とか Go とか Flutter とか Python絡みのメモを公開しています。

Goメモ-566 (Goに慣れた人がCに戻るとやりがちなこと)(ローカルアドレスを戻す)

関連記事

GitHub - devlights/blog-summary: ブログ「いろいろ備忘録日記」のまとめ

概要

以下、自分用のメモです。

大した話では無いのですが、個人的にやってしまいがちなミスの話。

Goに慣れてしまっている状態で、C言語のプログラムを作成してると、たまに「ローカルアドレスを返しちゃう」ってミスをやってしまいます。

C言語では絶対にやってはいけない以下のようなコードです。

#include <stdio.h>

int* getvalue(int x)
{
    return &x;
}

int main(void)
{
    int *p1 = getvalue(1);
    int *p2 = getvalue(2);
    printf("%d,%d\n", *p1, *p2);

    return 0;
}

当たり前ですが、関数内のローカル変数である x のアドレスを返している(つまりスタック上のアドレスを返している)ので、まともに動きません。

最悪コアダンプします。

これが、Goの場合は何の問題もないコードなんですね。

package main

import "fmt"

func getvalue(x int) *int {
    return &x
}

func main() {
    p1 := getvalue(1)
    p2 := getvalue(2)
    fmt.Printf("%d,%d\n", *p1, *p2)
}

Goには「エスケープ分析」という機能がありますので、上記のコードは何も問題ありません。結果も狙った通りになります。

GoとCって似てるので、パパっとCのコードを書いてるとついついGoでやってるように書いちゃうんですよね・・。

サンプル

ソースは上記のものです。Cの方は gcc と clang でビルドして実行してみています。

Taskfile.yml

# https://taskfile.dev

version: '3'

tasks:
  default:
    cmds:
      - task: show-ver
      - task: compile-c
      - task: compile-go
      - task: run_gcc
      - task: run_clang
      - task: run_go
  show-ver:
    silent: true
    cmds:
      - gcc --version | head -n 1
      - clang --version | head -n 1
  compile-c:
    dir: c
    cmds:
      - gcc -o ../app_gcc main.c
      - clang -o ../app_clang main.c
  compile-go:
    cmds:
      - go build -o app_go main.go
  run_gcc:
    internal: true
    cmds:
      - sh -c './app_gcc | true'
  run_clang:
    internal: true
    cmds:
      - ./app_clang
  run_go:
    internal: true
    cmds:
      - ./app_go

実行

$ task
gcc (Ubuntu 11.4.0-1ubuntu1~22.04) 11.4.0
Ubuntu clang version 14.0.0-1ubuntu1.1

task: [compile-c] gcc -o ../app_gcc main.c
main.c: In function ‘getvalue’:
main.c:5:12: warning: function returns address of local variable [-Wreturn-local-addr]
    5 |     return &x;
      |            ^~
task: [compile-c] clang -o ../app_clang main.c
main.c:5:13: warning: address of stack memory associated with parameter 'x' returned [-Wreturn-stack-address]
    return &x;
            ^
1 warning generated.
task: [compile-go] go build -o app_go main.go
task: [run_gcc] sh -c './app_gcc | true'
Segmentation fault
task: [run_clang] ./app_clang
2,2
task: [run_go] ./app_go
1,2

上記を見ると、gccでコンパイルしたものはコアダンプしていますが、clangでコンパイルしたものはコアダンプせずに実行出来ていますね。とはいえ、2,2 と出ていますので、ローカル変数のアドレスの位置が再利用されて値が上書きされてしまっています。どちらにせよ、アウトですが。両方のコンパイラもちゃんと警告だしてくれていますので、普通はここで気づきます。

Goの方は普通に 1, 2 と表示されてます。呼び出しのたびに値がヒープにエスケープされているので、ちゃんと表示されてるということです。

参考情報

try-golang/examples/singleapp/return_local_address at main · devlights/try-golang · GitHub

Goのおすすめ書籍


過去の記事については、以下のページからご参照下さい。

サンプルコードは、以下の場所で公開しています。