Writing Portable C Code with SAS/C

Vol 1 Ch.13 addresses the question every AmigaOS developer eventually asks: how do I write code that can move to a different compiler, OS, or architecture? The chapter surveys the portability pitfalls specific to SAS/C 6.58 and AmigaOS, and the constructs that travel well. Source: Vol 1 Ch.13, pp. 199–213.

The portability layers

Three concentric circles, from broadest to narrowest:

+------------------------------------------------+
|  Standard C (K&R, C89, C99)                   |   ← portable everywhere
+------------------------------------------------+
|  SAS/C extensions ( __saveds, #pragma, ... )  |   ← portable across Amiga
+------------------------------------------------+
|  AmigaOS specifics ( dos.library, Intuition )  |   ← portable across 68k Amiga only
+------------------------------------------------+

The portability goal determines which layer the code lives in:

  • Cross-platform (Amiga, Linux, Mac): standard C + minimal platform-specific shims
  • Cross-Amiga (68k AmigaOS, AmigaOS 4, MorphOS, AROS): SAS/C extensions OK, AmigaOS specifics must be wrapped
  • Single-target (a specific AmigaOS version): direct API calls OK

Standard C vs SAS/C

Things that work on any C89/C99 compiler:

  • All of standard C: control flow, structs, pointers, arrays, functions
  • <stdio.h>, <stdlib.h>, <string.h>, <math.h>, <time.h>
  • enum, union, typedef
  • const, volatile

Things that don't:

SAS/C construct Why not portable
__saveds Compiler-specific keyword
__interrupt Compiler-specific keyword
__regargs / __stdargs Compiler-specific calling convention
__asm / #asm / #endasm Inline assembly is compiler-specific
#pragma libcall SAS-specific pragma
#pragma amicall SAS-specific (Aztec compatibility)
#pragma msg SAS-specific diagnostic control
BPTR, BSTR AmigaOS BCPL types
ZERO, NULL (as BPTR) ZERO is AmigaOS-specific

Portability wrapping patterns

Header shims

Create a single header that defines the portability layer:

/* portability.h — included by all source files */

#if defined(__SASC)
#  include <proto/exec.h>
#  include <proto/dos.h>
   /* SAS/C specifics OK here */
#elif defined(__GNUC__)
#  include <exec/types.h>
   /* GCC specifics here */
#endif

/* Common typedefs */
typedef unsigned char  uint8;
typedef unsigned short uint16;
typedef unsigned long  uint32;

/* Compiler-portable inlining */
#if defined(__SASC)
#  define INLINE __inline
#elif defined(__GNUC__)
#  define INLINE static inline
#else
#  define INLINE static
#endif

AmigaOS function wrapping

Wrap AmigaOS-specific code in adapter functions, keep the rest portable:

/* portable.c */
#include "portability.h"

int read_file(const char *path, void *buf, int len)
{
#ifdef AMIGA
    BPTR fh = Open((STRPTR)path, MODE_OLDFILE);
    if (!fh) return -1;
    LONG got = Read(fh, buf, len);
    Close(fh);
    return (int)got;
#else
    FILE *f = fopen(path, "rb");
    if (!f) return -1;
    size_t got = fread(buf, 1, len, f);
    fclose(f);
    return (int)got;
#endif
}

The rest of the program uses read_file() and never sees Open/Read/fopen/fread.

Conditional compilation discipline

Put platform-specific code in a single port_*.c file, not scattered through the codebase:

project/
├── portability.h
├── port_amiga.c      # AmigaOS implementation
├── port_linux.c      # POSIX implementation
├── port_macos.c      # macOS implementation
└── ... rest of code, all using portability.h

The build system picks the right port_*.c based on TARGET.

Porting from SAS/C to GCC

If you have SAS/C code and want to move to GCC (e.g., for cross-compilation to AmigaOS 4 or MorphOS), the main work is:

  1. Replace __saveds with __attribute__((saveds)) or static __attribute__((saveds)) (the attribute is non-standard but supported by GCC for AmigaOS targets).
  2. Replace __interrupt with __attribute__((interrupt)).
  3. Replace #pragma libcall FooBase 1 123 with LONG Foo(...); plus a manual jump-table offset, or use the NDK proto/ headers that already declare them.
  4. Replace __regargs with __attribute__((regparm(3))).
  5. Replace #pragma msg with __attribute__((warning("..."))) or #pragma GCC warning.
  6. Replace BPTR with APTR if not actually a BCPL pointer.
  7. Replace inline assembly with GCC inline assembly (different syntax).
  8. Update smakefile → Makefile.

A typical porting effort: a few days for a small program (under 5000 lines), a few weeks for a large one.

Porting from GCC to SAS/C

The reverse direction is harder because GCC supports more extensions and a more recent C standard. Things to watch for:

  • C99/C11 features not in SAS/C 6.58: // comments (SAS does support them), designated initializers (supported), compound literals (not supported), inline (use __inline), restrict (no equivalent), variable-length arrays (no equivalent), <stdint.h> (no exact equivalent — define uint32_t etc. manually).
  • GCC inline assembly syntax is different — rewrite as .s files.
  • __attribute__((packed)) — SAS/C has no equivalent; use __attribute__ if GCC, manual packing otherwise.

Porting the build system

smake and make are 80% similar. The main porting steps:

  1. Tabs. smake requires tabs; make accepts spaces but is more portable with tabs.
  2. Shell commands. delete (Amiga) → rm (POSIX).
  3. Path separators. / on AmigaOS since 2.0, but older code uses :. Standardize on /.
  4. Assigns. C:, INCLUDE:, LIBS: are Amiga-specific. Replace with absolute paths in a cross-platform Makefile.
  5. Path wildcards. Amiga's #? and ? don't match * and ? in Unix shells. Use wildcard (Make 3.81+) or list files explicitly.

The portability test

A practical rule: can you grep for the compiler-specific bits?

grep -E '__saveds|__interrupt|__regargs|#pragma libcall|BPTR' src/*.c

Any hits are portability issues. Either remove them, wrap them, or document why they are necessary.

What SAS/C gives you that the standard doesn't

Some SAS/C features have a portability cost but are still worth using on Amiga-only projects:

  • __saveds — required for correct callback behavior. No portable equivalent; the __attribute__ form is GCC-specific.
  • #pragma libcall — drastically cleaner than manual offset declarations. Use it.
  • __saveds data segment setup — fastest way to do base-relative addressing. Use it.
  • CodeProbe debugger — best AmigaOS-aware debugger of its era.

See Also


Sources: SAS Institute Inc., "SAS/C Development System User's Guide, Volume 1" Ch.13, Version 6.50 (1993), pp. 199–213. Raw: raw/sasc/sasc-portable-c-extract.md Updated: 2026-10-02