# Primitive types


Primitive types are small value types that are copied to the stack (or a CPU register) when you use them as function parameters or return values. You don't need to pin or otherwise manage the lifespan of primitive types when calling unmanaged functions.

C# has different names for many primitive types compared to C and other languages. Refer to [Platform invoke data types](https://learn.microsoft.com/en-us/dotnet/framework/interop/marshalling-data-with-platform-invoke#platform-invoke-data-types) for a list of the unmanaged C language types that correspond to the managed C# types.

The following table shows the C type that each C# type marshals to *by default*. Default marshalling is a starting point, not a guarantee: it can vary by platform and by context (parameter, return value, or struct field). For example, `System.Boolean` marshals to a 4-byte value by default, and `System.Char` marshals according to the `CharSet` of the call. Use [MarshalAsAttribute](https://learn.microsoft.com/en-us/dotnet/api/system.runtime.interopservices.marshalasattribute) to control the exact representation when the default isn't what you need.

Some examples include:

| **C# base type** | **C# alias name** | **Unmanaged C type** |
| ---------------- | ----------------- | -------------------- |
| System.Int32     | int               | int32\_t             |
| System.Single    | float             | float                |
| System.Char      | char              | char                 |
| System.Boolean   | bool              | int32\_t             |
| System.Byte      | byte              | unsigned char        |

> **Note:**
>
> Use fixed-width C types (such as `int32_t` from `<stdint.h>`) in your native code rather than `int` or `long`. The size of `long` in particular varies by platform: it's 32-bit on Windows but 64-bit on most other desktop and mobile platforms. If the managed and unmanaged sides disagree about a type's size, the data is corrupted.
