Accessing address of a C global const variable - cannot pass immutable value as inout argument

Context: I want to access the address of a C global const variable in Swift.

Here is my simple project:

  • Package.swift
// swift-tools-version: 5.9
// The swift-tools-version declares the minimum version of Swift required to build this package.

import PackageDescription

let package = Package(
    name: "TestApp",
    platforms: [.macOS(.v14), .iOS(.v13), .watchOS(.v6)],
    products: [
        .executable(name: "MyApp", targets: ["MyApp"])
    ],
    targets: [
        .executableTarget(name: "MyApp",
            dependencies: [
                .target(name: "MyCLib")
            ]
        ),
        .target(name: "MyCLib")
    ]
)
  • Sources/MyCLib/my_c_lib.c
#include <stdio.h>

const int hello_value = 2;

void hello_world() {
    printf("Hello World from MyCLib (hello_value:0x%p)\n", &hello_value);
}
  • Sources/MyCLib/include/MyCLib.h
extern const int hello_value;

void hello_world();
  • Sources/MyApp/main.swift
import MyCLib

withUnsafePointer(to: &hello_value) { helloValuePtr in
    print("helloValuePtr: \(helloValuePtr)")
}

Trying the code above I have the error:

% swift run
Building for debugging...
error: emit-module command failed with exit code 1 (use -v to see invocation)
/Users/olivier/dev/test_c_const/Sources/MyApp/main.swift:3:23: error: cannot pass immutable value as inout argument: 'hello_value' is a 'let' constant
withUnsafePointer(to: &hello_value) { helloValuePtr in
                      ^~~~~~~~~~~~
/Users/olivier/dev/test_c_const/Sources/MyApp/main.swift:3:23: error: cannot pass immutable value as inout argument: 'hello_value' is a 'let' constant
withUnsafePointer(to: &hello_value) { helloValuePtr in
                      ^~~~~~~~~~~~
error: fatalError
  • Changing the code such as:
import MyCLib

hello_world()

withUnsafePointer(to: hello_value) { helloValuePtr in
    print("helloValuePtr: \(helloValuePtr)")
}

... it builds but it gaves me the wrong answer:

% swift run
Building for debugging...
[3/3] Linking MyApp
Build complete! (1.26s)
Hello World from MyCLib (hello_value:0x0x101876f84)
helloValuePtr: 0x00007ff7be68c198

I suspect it gives me the address of the swift copy of the C variable hello_world.

A workaround is obviously to modify the C code to expose the address of the global variable const int* hello_value_ptr = &hello_value; but this example is only for this post - in the big picture I cannot modify it.

You could cheat on the C side: expose the variable as "extern int hello_value" (without const), say, in a bridging header, so Swift will see this variable as "non const" whilst C will still see this variable as "const".

Otherwise this is impossible.

Why?

1 Like

Thanks @tera, I confirm this workaround works. I guess if it is impossible, I will have to do with this workaround.

To answer your why, if I remember the context (this is an old issue I had, I am at the moment stuck on this issue) I had this use case in C:

typedef struct {
  const char* name;
  int age;
} student_t;

const student_t my_students[] = {
  { .name: "Tom", age: 18 },
  { .name: "Bob", age: 18 },
  { .name: "John", age: 19 }
};

void process_student(student_t* student) {
  (...)
}

And I wanted to call process_student() from Swift with one of the my_students.

Does "process_student" care about address being passed and do something differently if I pass:

student_t newTom = my_students[0];
process_student(&newTom);

instead of:

process_student(&my_students[0]);

I don't think so. And I get your point - passing a (Swift) copy of the C structure instead of the C value itself in case process_student() does not care about the C address.

Another workaround, if you get to write C code: add a static C function that returns the address of the global.

There is not any way to do this in Swift that I’m aware of; Swift’s & does inout and (as you noticed) only in a few circumstances is that guaranteed to produce a stable address.

Does it even need to be a function? Can't it just be a global constant like

// in header
extern const int *const hello_address;

// in C file
const int *const hello_address = &hello_value;

That should work too. You definitely want it to be static though; otherwise you’re making promises about that symbol name that you don’t need, which you then do entirely in a header rather than needing an extra .c file.

1 Like