Cannot take the address of, get the size of, or de

2019-01-17 17:04发布

I've done a fair bit of research, but am stuck now as to why I'm still getting this error. I have a struct with the following attributes:

struct Account
{
    //private attributes
    private double mBalance;
    private int mAccountNumber;
    private string mName;
    private string mDateCreated;
}

and am trying to do the following:

class BankManager
{
    //private attributes
    private unsafe Account *mAccounts;
    private unsafe bool *mAccountsAvailable;
    private int mNumberAccounts;
}

Even after turning my class Account to a struct, using "unsafe" for the attributes in class BankManager, and telling the compiler it can use unsafe code (in properties -> Build), I'm still getting this error at

*mAccounts

Any ideas as to why? I'm pretty sure all the types I'm using in the struct are legal to have pointers to in c#. Thanks in advance!

5条回答
唯我独甜
2楼-- · 2019-01-17 17:34

You are wrong about the struct containing types that can have pointers, because a string is a managed type which cannot have a pointer reference.

查看更多
对你真心纯属浪费
3楼-- · 2019-01-17 17:47

Use private unsafe fixed char mName[126];
Strings are managed types, and so are non-fixed arrays.

查看更多
SAY GOODBYE
4楼-- · 2019-01-17 17:50

Strings are reference types in .NET and are non-blittable for struct pointers. See Blittable and Non-Blittable Types for a list of value types for what you want to do.

Unless you have special business requirements, you should stick with managed memory for maintainability and general sanity.

查看更多
放我归山
5楼-- · 2019-01-17 17:57

Managed data does not stay in a fixed location, as the copying collector can move things around. This is equally true of managed boxed value types. Managed unboxed value-types can only live on the stack or inside other objects. They only have fixed locations if they are in the stack.

In order to create a heap-allocated struct which has a fixed location from which you can take a pointer which will continue to be valid, you have to allocate it in unmanaged memory. However, once you allocate it in unmanaged memory, you can't put managed pointers in it anymore (aka, you can't use string), because the garbage collector won't know about those pointers so it won't update them when it moves managed objects around during compaction.

For example, this is a valid (though not necessarily good) thing to do:

[StructLayout(LayoutKind.Sequential, Pack=1)]
public unsafe struct Account {
    public int a;
    public char* mName;
}
public class BankManager {
    private unsafe Account* mAccounts;
    public unsafe int GetA() {
        return mAccounts->a;
    }
    public unsafe BankManager() {
        mAccounts = (Account*)Marshal.AllocHGlobal(sizeof(Account));
    }
    unsafe ~BankManager() {
        if (mAccounts != null) {
             Marshal.FreeHGlobal((IntPtr)mAccounts);
             mAccounts = null;
        }
    }
}

Here we have allocated the struct in unmanaged memory. This allows us to hold a pointer to it which we know does not change or move. We have to manually free the struct when we are done with it. The same manual alloc/free and marshalling would need to be done to mAccounts->mName, since it is how an unmanaged char* (c-style string).

I made the struct have packed sequential layout to make the behavior of this code closer to it's C-counterpart, because code like the above would normally only be used when doing interop with a native C DllImport entrypoint that expects a particular struct layout.

查看更多
Summer. ? 凉城
6楼-- · 2019-01-17 17:58

The strings in the Account class cause this problem. To understand why, you need to understand how the garbage collector works. It discovers garbage by tracking references to objects. The mName and mDateCreated are such references. The mBalance and mAccountNumber are not, those fields are value types. And, most importantly, the BankManager.mAccounts field is not, it is a pointer.

So the compiler can tell up front that the garbage collector will never be able to see the string references. Because the only way to do so is to go through the mAccount field and its not a reference.

The only cure for this is to limit yourself strictly to value types. The only way to do that for strings is to allocate them in unmanaged memory with, say, Marshal.StringToCoTaskMemUni() and store the IntPtr in the field. It is now out of reach from the garbage collector and cannot get moved by it. You'll now also have the burden of releasing that string.

Clearly that's not practical and prone to cause leaks, the kind of problem that's so common in C programs. Not sure why you are pursuing this at all but do keep in mind that a reference to an object is already a simple pointer so you are not gaining anything by using pointers yourself.

查看更多
登录 后发表回答