Pointers and unsafe code in C#
Pointers and unsafe code in C# allow you to operate directly with memory, much like in C language or C++. Although this feature differs from C# and the Common Language Runtime’s (CLR) inherent type-safe and secure nature, it might be useful in some situations, such performance optimisation or compatibility with unmanaged libraries.
Understanding Unsafe Code
Using a pointer variable in a block of code or procedure is considered unsafe coding. To allow the usage of the unsafe subset of C#, the unsafe keyword is used to indicate a type, method, or inline code block. This subset contains C-like arrays, pointers, and stack allocation (stackalloc).
Key characteristics and implications of unsafe code
Non-Verifiable: Because unsafe code cannot be verified by the CLR, its safety cannot be ensured.
Security and Stability Risks: Bypassing the CLR’s supervision over Microsoft Intermediate Language (MSIL) code, which often stops dubious actions, using unsafe code exposes security and stability problems. Ensuring accuracy and avoiding security threats or pointer mistakes are the developer’s responsibilities.
Compilation Requirement: The /unsafe command-line switch must be explicitly used with the C# compiler (csc) in order to build unsafe code. The “Build” tab in the project settings of the Visual Studio IDE allows you to enable this option.
Full Trust Requirement: For the running assembly to run dangerous code, the CLR needs complete trust.
Performance and Interoperability: Unsafe code can improve application speed by eliminating array bounds checks, notwithstanding the hazards. It is also necessary when using native functions that need pointers, such as Win32 API calls.
Understanding Pointers
A variable that holds another variable’s memory location is called a pointer. Although they can be used in unsafe code blocks, pointer types are not supported by default in C# for data manipulation, unlike C++. C# pointers are just as capable as their C or C++ counterparts.
Pointer Declaration: Type* identifier; is the common form used to declare a pointer type. In C# declarations, the type includes the asterisk *.
- Example:
int* ip;
(pointer to an integer) - Example:
char* ch;
(pointer to a character)
Pointer Types and Conversions
- It is possible for pointers to point to any enum type, sbyte, byte, short, ushort, int, uint, long, ulong, char, float, double, decimal, bool, or any user-defined struct that only includes unmanaged kinds.
- Pointers are not supported by boxing or unpacking, and they do not inherit from objects.
- Pointer types and integral types can be converted (explicitly) as well as between other pointer types.
- A void* pointer, which was carried over from C/C++, is size- and type-independent. Any pointer type can be implicitly changed to void*, but the opposite must be explicitly changed. Microsoft, however, discourages specifying a void pointer type.
Pointers and Memory Management (fixed keyword)
The fixed keyword for memory management and pointers is: Using pointers with managed objects (such as arrays or strings) requires the use of the fixed keyword.
- The garbage collector in safe code can freely relocate items in memory to maximise available space.
- The garbage collector must not change the memory location that a pointer points to when it is utilised.
- To stop the garbage collector from moving the object, the fixed statement pins it in memory for the life of the fixed block. Garbage collection returns to the memory when the fixed block is finished.
- Fixed can only be applied to fields inside structs or, in an unsafe situation, to get a reference to an array element.
Operations on Pointers
Pointers can be used with a number of operators in a risky situation:
Indirection Operator (*): The pointer variable is used to retrieve the material at the memory address.
Example: *p
.
Member Access Operator (->): Used to type a pointer to a member of a struct.
Example: ptr->X
.
Array Element Access ([]): Pointers, like C/C++ arrays, can be indexed with square brackets. *(p+e) is the equivalent of this operation (p[e]), which does not check for bounds.
Address-of Operator (&): used for obtaining a variable’s memory location.
Example: &var
.
Increment (++) and Decrement (–): Change the sizeof(pointer-type) pointer position. An int* pointer is moved four bytes while incrementing.
Arithmetic Operations (+, -): The pointer is moved by n * sizeof(p) bytes to allow for the addition or subtraction of numeric values.
Comparison Operators (==, !=, <, >, <=, >=): Compare the unsigned integer addresses that are kept in pointers.
Code Examples
Simple Pointer Usage
This example illustrates the value and address of a variable and demonstrates fundamental pointer declaration and usage:
using System;
namespace UnsafeCodeApplication
{
class Program
{
static unsafe void Main(string[] args) // 'unsafe' modifier on the method
{
int var = 20;
int* p = &var; // '&' obtains the address of 'var'
Console.WriteLine("Data is: {0} ", var);
Console.WriteLine("Address is: {0}", (int)p); // Cast pointer to int to display address
Console.ReadKey();
}
}
}
Output
main.cs(6,16): error CS0227: Unsafe code requires the `unsafe' command line option to be specified
Compilation failed: 1 error(s), 0 warnings
Conclusion
Although C# places a strong emphasis on managed memory and type safety, unsafe code and pointers provide strong capabilities for situations that call for direct contact with unmanaged code or low-level memory manipulation. Since these functionalities circumvent many of the security checks offered by the.NET runtime, the programmer bears greater responsibility.
You can also read What Is Mean By Multithreading In C#, Concepts & Components