Skip to main content
added 257 characters in body
Source Link
Davislor
  • 1.2k
  • 2
  • 10

No, and Some Systems Used the Same Convention

K&R C had only a single calling convention, which is how printf() could be called without any special syntax or function prototype. In fact, since there were no function prototypes in C until ANSI C, there could not possibly have been different calling conventions for any implementation that also needed to compile K&R-style code, for example:

struct FILE;

struct FILE* fprintf();
int fopen();

int main() {
    struct FILE *outfile;
    outfile = fopen("hello.txt", 3);
    /* WONTFIX: Real code should check for errors. */
    fprintf(outfile, "%s\n", "hello, world!");
    return 0;
}

There is simply no way for the compiler to tell that fprintf is variadic and fopen is not (at least, not for code other than the standard library, without doing more whole-program analysis than compilers back then did).

On 16-bit x86, this led to C having a different calling convention from Pascal (with C passing its arguments in reverse order, so that the first argument of a variadic function, such as its format string, would always be at a known location, immediately before the return address on top of the stack). This is why ANSI C required varargs functions to have at least one non-variadic parameter, and for the variadic parameters to come last.

The va_start and va_arg macros are the way they are to allow them to be implemented by taking the address of the last non-variadic argument on the call stack and successively adding the size of each variadic argument to obtain a pointer to the next.

Most of the special rules for variadic functions, such as the default argument promotions, exist solely for the sake of backward-compatibility with K&R C. For example, arguments narrower than an int widen to int because an int represented the smallest object that could be passed on the stack on word-addressed machines, and because this saved an instruction on machines that had no byte or half-word instructions. Similarly, float widens to double partly to avoid rounding error, but mainly because that was how it worked on the DEC PDP-8. The rules for pointer conversions were intended to enable K&R-style cpde such as &dest == memcpy(&dest, &src, sizeof(dest)); that worked on K&R C (because there was no type-checking of function arguments, and all object pointers were cast to character pointers when passed as function arguments) to also compile on ANSI C, with the function prototype void* memcpy(const void*, void*, size_t);.

No, and Some Systems Used the Same Convention

K&R C had only a single calling convention, which is how printf() could be called without any special syntax or function prototype. In fact, since there were no function prototypes in C until ANSI C, there could not possibly have been different calling conventions for any implementation that also needed to compile K&R-style code, for example:

struct FILE;

struct FILE* fprintf();
int fopen();

int main() {
    struct FILE *outfile;
    outfile = fopen("hello.txt", 3);
    /* WONTFIX: Real code should check for errors. */
    fprintf(outfile, "%s\n", "hello, world!");
    return 0;
}

There is simply no way for the compiler to tell that fprintf is variadic and fopen is not (at least, not for code other than the standard library, without doing more whole-program analysis than compilers back then did).

On 16-bit x86, this led to C having a different calling convention from Pascal (with C passing its arguments in reverse order, so that the first argument of a variadic function, such as its format string, would always be at a known location, immediately before the return address on top of the stack). This is why ANSI C required varargs functions to have at least one non-variadic parameter, and for the variadic parameters to come last.

Most of the special rules for variadic functions, such as the default argument promotions, exist solely for the sake of backward-compatibility with K&R C. For example, arguments narrower than an int widen to int because an int represented the smallest object that could be passed on the stack on word-addressed machines, and because this saved an instruction on machines that had no byte or half-word instructions. Similarly, float widens to double partly to avoid rounding error, but mainly because that was how it worked on the DEC PDP-8. The rules for pointer conversions were intended to enable K&R-style cpde such as &dest == memcpy(&dest, &src, sizeof(dest)); that worked on K&R C (because there was no type-checking of function arguments, and all object pointers were cast to character pointers when passed as function arguments) to also compile on ANSI C, with the function prototype void* memcpy(const void*, void*, size_t);.

No, and Some Systems Used the Same Convention

K&R C had only a single calling convention, which is how printf() could be called without any special syntax or function prototype. In fact, since there were no function prototypes in C until ANSI C, there could not possibly have been different calling conventions for any implementation that also needed to compile K&R-style code, for example:

struct FILE;

struct FILE* fprintf();
int fopen();

int main() {
    struct FILE *outfile;
    outfile = fopen("hello.txt", 3);
    /* WONTFIX: Real code should check for errors. */
    fprintf(outfile, "%s\n", "hello, world!");
    return 0;
}

There is simply no way for the compiler to tell that fprintf is variadic and fopen is not (at least, not for code other than the standard library, without doing more whole-program analysis than compilers back then did).

On 16-bit x86, this led to C having a different calling convention from Pascal (with C passing its arguments in reverse order, so that the first argument of a variadic function, such as its format string, would always be at a known location, immediately before the return address on top of the stack). This is why ANSI C required varargs functions to have at least one non-variadic parameter, and for the variadic parameters to come last.

The va_start and va_arg macros are the way they are to allow them to be implemented by taking the address of the last non-variadic argument on the call stack and successively adding the size of each variadic argument to obtain a pointer to the next.

Most of the special rules for variadic functions, such as the default argument promotions, exist solely for the sake of backward-compatibility with K&R C. For example, arguments narrower than an int widen to int because an int represented the smallest object that could be passed on the stack on word-addressed machines, and because this saved an instruction on machines that had no byte or half-word instructions. Similarly, float widens to double partly to avoid rounding error, but mainly because that was how it worked on the DEC PDP-8. The rules for pointer conversions were intended to enable K&R-style cpde such as &dest == memcpy(&dest, &src, sizeof(dest)); that worked on K&R C (because there was no type-checking of function arguments, and all object pointers were cast to character pointers when passed as function arguments) to also compile on ANSI C, with the function prototype void* memcpy(const void*, void*, size_t);.

added 2 characters in body
Source Link
Davislor
  • 1.2k
  • 2
  • 10

No, and Some Systems Used the Same Convention

K&R C had only a single calling convention, which is how printf() could be called without any special syntax or function prototype. In fact, since there were no function prototypes in C until ANSI C, there could not possibly have been different calling conventions for any implementation that also needed to compile K&R-style code, for example:

struct FILE;

struct FILE* fprintf();
int fopen();

int main() {
    struct FILE *outfile;
    outfile = fopen("hello.txt", 3);
    /* WONTFIX: Real code should check for errors. */
    fprintf(outfile, "%s\n", "hello, world!");
    return 0;
}

There is simply no way for the compiler to tell that fprintf is variadic and fopen is not (at least, not for code other than the standard library, without doing more whole-program analysis than compilers back then did).

On 16-bit x86, this led to C having a different calling convention from Pascal (with C passing its arguments in reverse order, so that the first argument of a variadic function, such as its format string, would always be at a known location, immediately before the return address on top of the stack). This is why ANSI C required varargs functions to have at least one non-variadic parameter, and for the variadic parameters to come last.

Most of the special rules for variadic functions, such as the default argument promotions, exist solely for the sake of backward-compatibility with K&R C. For example, arguments narrower than an int widen to int because an int represented the smallest object that could be passed on the stack on word-addressed machines, and because this saved an instruction on machines that had no byte or half-word instructions. Similarly, float widens to double partly to avoid rounding error, but mainly because that was how it worked on the DEC PDP-8. The rules for pointer conversions were intended to enable K&R-style cpde such as foo&dest === memcpy(&dest, &src, sizeof(dest)); that worked on K&R C (because there was no type-checking of function arguments, and all object pointers were cast to character pointers when passed as function arguments) to also compile on ANSI C, with the function prototype void* memcpy(const void*, void*, size_t);.

No, and Some Systems Used the Same Convention

K&R C had only a single calling convention, which is how printf() could be called without any special syntax or function prototype. In fact, since there were no function prototypes in C until ANSI C, there could not possibly have been different calling conventions for any implementation that also needed to compile K&R-style code, for example:

struct FILE;

struct FILE* fprintf();
int fopen();

int main() {
    struct FILE *outfile;
    outfile = fopen("hello.txt", 3);
    /* WONTFIX: Real code should check for errors. */
    fprintf(outfile, "%s\n", "hello, world!");
    return 0;
}

There is simply no way for the compiler to tell that fprintf is variadic and fopen is not (at least, not for code other than the standard library, without doing more whole-program analysis than compilers back then did).

On 16-bit x86, this led to C having a different calling convention from Pascal (with C passing its arguments in reverse order, so that the first argument of a variadic function, such as its format string, would always be at a known location, immediately before the return address on top of the stack). This is why ANSI C required varargs functions to have at least one non-variadic parameter, and for the variadic parameters to come last.

Most of the special rules for variadic functions, such as the default argument promotions, exist solely for the sake of backward-compatibility with K&R C. For example, arguments narrower than an int widen to int because an int represented the smallest object that could be passed on the stack on word-addressed machines, and because this saved an instruction on machines that had no byte or half-word instructions. Similarly, float widens to double partly to avoid rounding error, but mainly because that was how it worked on the DEC PDP-8. The rules for pointer conversions were intended to enable K&R-style cpde such as foo = memcpy(&dest, &src, sizeof(dest)); that worked on K&R C (because there was no type-checking and all object pointers were cast to character pointers when passed as function arguments) to also compile on ANSI C, with the function prototype void* memcpy(const void*, void*, size_t);.

No, and Some Systems Used the Same Convention

K&R C had only a single calling convention, which is how printf() could be called without any special syntax or function prototype. In fact, since there were no function prototypes in C until ANSI C, there could not possibly have been different calling conventions for any implementation that also needed to compile K&R-style code, for example:

struct FILE;

struct FILE* fprintf();
int fopen();

int main() {
    struct FILE *outfile;
    outfile = fopen("hello.txt", 3);
    /* WONTFIX: Real code should check for errors. */
    fprintf(outfile, "%s\n", "hello, world!");
    return 0;
}

There is simply no way for the compiler to tell that fprintf is variadic and fopen is not (at least, not for code other than the standard library, without doing more whole-program analysis than compilers back then did).

On 16-bit x86, this led to C having a different calling convention from Pascal (with C passing its arguments in reverse order, so that the first argument of a variadic function, such as its format string, would always be at a known location, immediately before the return address on top of the stack). This is why ANSI C required varargs functions to have at least one non-variadic parameter, and for the variadic parameters to come last.

Most of the special rules for variadic functions, such as the default argument promotions, exist solely for the sake of backward-compatibility with K&R C. For example, arguments narrower than an int widen to int because an int represented the smallest object that could be passed on the stack on word-addressed machines, and because this saved an instruction on machines that had no byte or half-word instructions. Similarly, float widens to double partly to avoid rounding error, but mainly because that was how it worked on the DEC PDP-8. The rules for pointer conversions were intended to enable K&R-style cpde such as &dest == memcpy(&dest, &src, sizeof(dest)); that worked on K&R C (because there was no type-checking of function arguments, and all object pointers were cast to character pointers when passed as function arguments) to also compile on ANSI C, with the function prototype void* memcpy(const void*, void*, size_t);.

added 138 characters in body
Source Link
Davislor
  • 1.2k
  • 2
  • 10

No, and Some Systems Did Not Have ThemUsed the Same Convention

K&R C had only a single calling convention, which is how printf() could be called without any special syntax or function prototype. In fact, since there were no function prototypes in C until ANSI C, there could not possibly have been different calling conventions for any implementation that also needed to compile K&R-style code, for example:

struct FILE;

struct FILE* fprintf();
int fopen();

int main() {
    struct FILE *outfile;
    outfile = fopen("hello.txt", 3);
    /* WONTFIX: Real code should check for errors. */
    fprintf(outfile, "%s\n", "hello, world!");
    return 0;
}

There is simply no way for the compiler to tell that fprintf is variadic and fopen is not (at least, not for code other than the standard library, without doing more whole-program analysis than compilers back then did).

On 16-bit x86, this led to C having a different calling convention from Pascal (with C passing its arguments in reverse order, so that the first argument of a variadic function, such as its format string, would always be at a known location, immediately before the return address on top of the stack). This is why ANSI C required varargs functions to have at least one non-variadic parameter, and for the variadic parameters to come last.

Most of the special rules for variadic functions, such as the default argument promotions, exist solely for the sake of backward-compatibility with K&R C. For example, arguments narrower than an int widen to int because an int represented the smallest object that could be passed on the stack on word-addressed machines, and because this saved an instruction on machines that had no byte or half-word instructions. Similarly, float widens to double partly to avoid rounding error, but mainly because that was how it worked on the DEC PDP-8. The rules for pointer conversions were intended to enable K&R-style cpde such as foo = memcpy(&dest, &src, sizeof(dest)); that worked on K&R C (because there was no type-checking and all object pointers were widenedcast to character pointers when passed as function arguments) to also compile on ANSI C, with the function prototype void* memcpy(const void*, void*, size_t);.

No, and Some Systems Did Not Have Them

K&R C had only a single calling convention, which is how printf() could be called without any special syntax or function prototype. In fact, since there were no function prototypes in C until ANSI C, there could not possibly have been different calling conventions for any implementation that also needed to compile K&R-style code, for example:

struct FILE;

struct FILE* fprintf();
int fopen();

int main() {
    struct FILE *outfile;
    outfile = fopen("hello.txt", 3);
    /* WONTFIX: Real code should check for errors. */
    fprintf(outfile, "%s\n", "hello, world!");
    return 0;
}

There is simply no way for the compiler to tell that fprintf is variadic and fopen is not (at least, not for code other than the standard library, without doing more whole-program analysis than compilers back then did).

On 16-bit x86, this led to C having a different calling convention from Pascal (with C passing its arguments in reverse order, so that the first argument of a variadic function, such as its format string, would always be at a known location, immediately before the return address on top of the stack).

Most of the special rules for variadic functions, such as the default argument promotions, exist solely for the sake of backward-compatibility with K&R C. For example, arguments narrower than an int widen to int because an int represented the smallest object that could be passed on the stack on word-addressed machines, and because this saved an instruction on machines that had no byte or half-word instructions. Similarly, float widens to double partly to avoid rounding error, but mainly because that was how it worked on the DEC PDP-8. The rules for pointer conversions were intended to enable K&R-style cpde such as foo = memcpy(&dest, &src, sizeof(dest)); that worked on K&R C (because there was no type-checking and all pointers were widened to character pointers when passed as function arguments) to also compile on ANSI C, with the function prototype void* memcpy(const void*, void*, size_t);.

No, and Some Systems Used the Same Convention

K&R C had only a single calling convention, which is how printf() could be called without any special syntax or function prototype. In fact, since there were no function prototypes in C until ANSI C, there could not possibly have been different calling conventions for any implementation that also needed to compile K&R-style code, for example:

struct FILE;

struct FILE* fprintf();
int fopen();

int main() {
    struct FILE *outfile;
    outfile = fopen("hello.txt", 3);
    /* WONTFIX: Real code should check for errors. */
    fprintf(outfile, "%s\n", "hello, world!");
    return 0;
}

There is simply no way for the compiler to tell that fprintf is variadic and fopen is not (at least, not for code other than the standard library, without doing more whole-program analysis than compilers back then did).

On 16-bit x86, this led to C having a different calling convention from Pascal (with C passing its arguments in reverse order, so that the first argument of a variadic function, such as its format string, would always be at a known location, immediately before the return address on top of the stack). This is why ANSI C required varargs functions to have at least one non-variadic parameter, and for the variadic parameters to come last.

Most of the special rules for variadic functions, such as the default argument promotions, exist solely for the sake of backward-compatibility with K&R C. For example, arguments narrower than an int widen to int because an int represented the smallest object that could be passed on the stack on word-addressed machines, and because this saved an instruction on machines that had no byte or half-word instructions. Similarly, float widens to double partly to avoid rounding error, but mainly because that was how it worked on the DEC PDP-8. The rules for pointer conversions were intended to enable K&R-style cpde such as foo = memcpy(&dest, &src, sizeof(dest)); that worked on K&R C (because there was no type-checking and all object pointers were cast to character pointers when passed as function arguments) to also compile on ANSI C, with the function prototype void* memcpy(const void*, void*, size_t);.

Source Link
Davislor
  • 1.2k
  • 2
  • 10
Loading