On many native systems, a segmentation fault is an operating-system response to an invalid memory access. It is a symptom, not a diagnosis of the exact C mistake. In Circuit, programs execute as WebAssembly, so a related failure may appear as a runtime trap instead of a native signal.
Memory access has a contract
A valid access needs a live object of an appropriate type at the referenced location, with the operation inside its bounds. C does not attach a complete validity history to every pointer. A pointer’s declaration alone does not prove that its current value satisfies those requirements.
Undefined behavior means the C language does not promise a particular outcome for that operation. It may crash immediately, corrupt a later calculation, or appear to succeed in one build. Repeatedly obtaining the expected answer is not evidence that an out-of-bounds access is allowed.
Array bounds are a frequent starting point
For int values[3], valid indexes are zero through two. A loop with i <= 3 writes beyond the array. Another common mistake is allocating storage for n items but indexing it with a user-provided value without validating that value first.
Check both the lower and upper bounds when an index is signed. Converting a negative index to an unsigned size can turn it into a very large positive value. Prefer conditions expressed directly in the same validated representation you use for the access.
int values[3] = {10, 20, 30};
int index = 2;
if (index >= 0 && index < 3) {
printf("%d\n", values[index]);
}Uninitialized and null pointers
int *p; does not allocate an int or choose a safe object. Dereferencing p before initialization uses an indeterminate pointer. Initialize it to the address of an existing object, or use a checked allocation when dynamic storage is required.
NULL expresses that no object is designated. Check the result of malloc before dereferencing it. A null check is necessary in that situation, but it does not validate every possible non-null pointer. A freed pointer can remain non-null, and a pointer into the wrong object can also pass that test.
Lifetime mistakes survive the original scope
Returning the address of a local automatic variable leaves the caller with an address whose object has expired. A later function call may reuse that storage. The bug belongs to the returned pointer’s lifetime, not necessarily to the line where an eventual crash happens.
Using memory after free has the same broad problem: the old object is no longer available to you. Freeing the same allocation twice is another violation. Identify which part of the program owns each allocation and when that ownership ends. Clearing one pointer does not clear other aliases to the same released block.
Strings and input can cross boundaries
An unbounded scanf("%s", buffer) may write more characters than the buffer holds. strcpy also requires enough destination capacity. Use bounded input and account for the terminating null byte; a buffer of 20 bytes has room for at most 19 ordinary single-byte characters plus its terminator.
A string function may read beyond the object when a null terminator is missing. This is why manually filling every byte of a character array does not necessarily create a valid string. Track storage capacity and actual content length as distinct values.
Stack exhaustion and WebAssembly limits
Very deep recursion or very large automatic local arrays can exhaust stack space. Circuit configures a one-megabyte stack for generated programs, so a mathematically terminating recursive algorithm can still be unsuitable. Use an iterative approach or reduce the storage required by each call.
WebAssembly traps accesses outside its linear memory, but that boundary is not the same as every individual C object boundary. An invalid array access that lands elsewhere inside linear memory may not trap immediately. Circuit is therefore not a full memory-safety checker, and a successful run is not a substitute for bounds and lifetime reasoning.
Find the first invalid operation
Reduce the input, isolate the function, and inspect indexes, pointer initialization, and ownership. Add small diagnostic prints before suspicious operations, but remember that the visible crash can happen after earlier corruption. Fix the first invalid access rather than merely avoiding the final symptom.
For deeper native debugging, reproduce a small portable case with a local toolchain and an appropriate debugger or sanitizer build. Circuit does not currently provide an interactive memory debugger or sanitizer UI. Keep the browser test small and deterministic, and compare each access with the object’s known size and lifetime.