I have a function which converts a string of an unsigned int to its unsigned int counterpart.
I want to to be able to pass an unsigned type of ANY size to it as the container and specify a limit on how big of a value it is to hold. function prototype is this:
str_to_uint(void *tar, const char *str, const uint64_t lim)
uint64_t *tar is where the unsigned integer will be stored, *str is the string of the number and uint64_t lim is the limit of the size the *tar will be able to hold.
since sizeof(tar) is variable is it safe to cast *tar to an uint64_t * and then contain the converted variable in that? I know that it will always be smaller than the actual type anyway since I check for it with the lim variable.
is such a thing allowed?
basically it would boil down to this
I have a variable of an unsigned type where sizeof(variable) is 1, 2, 3 or 4.
I pass the variable to the function via (void *)&variable.
in the function I cast it to uint64_t * and write the detected variable into it. I make sure the detected variable is able to be written into the variable by checking if it is smaller or equal than lim
is this allowed?
code:
#include <stdio.h>
#include <inttypes.h>
#include <limits.h>
#include <stdlib.h>
static int str_to_uint(uint64_t *tar, const char *str, const uint64_t lim) {
char *eptr = NULL;
unsigned long long int temp = 0;
if (str == NULL) {
printf("str is a NULL pointer\n");
return -1;
}
temp = strtoull(str, &eptr, 10);
if (temp == 0 && eptr == str) {
printf("strtoull() conv err, %s\n", str);
return -1;
} else if (temp > lim) {
printf("strtoull() value to big to contain specified limit, %s\n", str);
return -1;
} else {
*tar = temp;
}
return 0;
}
int main() {
int ret;
uint8_t a;
uint16_t b;
uint32_t c;
uint64_t d;
ret = str_to_uint((void *)&a, "22", UINT8_MAX);
if (ret != 0) {
exit(1);
}
ret = str_to_uint((void *)&b, "22", UINT16_MAX);
if (ret != 0) {
exit(1);
}
ret = str_to_uint((void *)&c, "22", UINT32_MAX);
if (ret != 0) {
exit(1);
}
ret = str_to_uint((void *)&d, "22", UINT64_MAX);
if (ret != 0) {
exit(1);
}
printf("a = %"PRIu8"\nb = %"PRIu16"\nc = %"PRIu32"\nd = %"PRIu64"\n", a, b, c, d);
exit(0);
}
No, of course you can't do that.
If I call your function like this:
uint8_t my_little_value;
str_to_uint(&my_little_value, "4711", sizeof my_little_value);
Then you do
uint64_t *user_value = tar;
*user_value = ...;
Boom, you've overwritten a bunch of bytes you're not allowed to touch. Of course you knew this since I passed you the size of my variable, and you say you "make sure", but I don't see how you intend to do that if your approach is going to be treating tar as a uint64_t *.
I don't see why you can't just return the converted number, like strtoul() already does. That puts the responsibility for dealing with mismatch between storage location and potential precision to represent the converted number on the user (or even on the compiler!) where it belongs. Your proposed API is very error-prone and hard to understand.
At this line of code:
*tar = temp;
You are writing 8 bytes of data to a variable that may be less than 8 bytes in size. You need to handle each size separately, like this for 1 byte:
*(uint8_t *) tar = temp;
Related
Assuming there is a function like this
int foo (char** str, int x)
{
char* p = *str + x;
foo2(&p); // declared as int foo2 (char** );
}
(oversimplified of course, the real function is recursive and much more complicated)
I've tried to do this:
int foo (char** str, int x)
{
foo2(&(*str + x));
}
But the compiler failed with error:
error: lvalue required as unary '&' operand
Why did the compiler shoot out with this error and how do I pass the pointer to a pointer to string x-byte(s) forwards, without declaring a variable and use its own address?
EDIT
Seems like there is some misunderstanding so I will post a complete simulation of what I want to achieve.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
char* string = "This is a sample string.";
char* ptr;
int randomizer;
int receive_string (char* buffer, int size) // recv
{
int i = 0;
if(ptr == NULL)
ptr = string;
for(i = 0; *ptr != '\0' && i < size; ptr++)
{
if(randomizer == 2)
{
randomizer++;
break;
}
buffer[i] = *ptr;
i++;
randomizer++;
}
if(*ptr == '\0')
{
buffer[i] = *ptr;
i++;
}
return i;
}
int read_string (char* *buffer, int size, int alloc)
{
int bytes = 0;
printf("Reading string..\n");
if(*buffer == NULL && alloc == 1)
{
printf("Allocating buffer..\n");
*buffer = calloc(size, sizeof(char));
}
bytes = receive_string(*buffer, size);
if(bytes == (-1))
{
return(-1);
}
if(bytes == 0)
{
return 0;
}
if(bytes < size)
{
char* p = *buffer + bytes;
//int temp = read_string(&p, size - bytes, 0); // works
//int temp = read_string(&(char *){&(*buffer)[bytes]}, size - bytes, 0); // works
int temp = read_string(buffer + bytes, size - bytes, 0); // doesn't work
if(temp > 0)
bytes += temp;
else return bytes;
}
return bytes;
}
int main()
{
char* buffer = NULL;
int bytes = read_string(&buffer, strlen(string) + 1, 1);
printf("[%u][%s]\n", bytes, buffer);
if(buffer)
free(buffer);
return 0;
}
The randomizer is the dumbest quickie to "simulate" a recv() that can not receive all bytes. This implementation simulates recv() but instead of reading from a socket queue it reads from a global string.
(*str + x) is not an lvalue as it is a temporay value that does not have an address so you cannot take its address with &. Even if the compiler stored the value in a temporary variable in RAM so its address could be taken how would you reference its value afterwards if foo2() modified the contents of the temporay variable.
Therefore you need to store the value in a temporary variable yourself.
if you want to pass the pointer to pointer to the particular char
foo2(&(char *){&(*str)[x]});
or
I think the following code is what you are trying to do. For kicks, I made it recursive and tested it with the alphabet for a string. Variables cnt and lmt need to be global. It will show a shrinking string if you run it. Just be sure to keep p and lmt small enough to not overflow the string.
void foo(char *s, int p) {
cnt++;
printf("%s\n", s);
if(cnt != lmt) foo(&s[p], p);
}
I've written the following C code to compare two areas of memory and check if they are identical
#include <stdio.h>
struct s1 {
int a, b;
char c;
} s1;
int memcmpwannabe(void* value1, void *value2, int size1, int size2) {
if(size1 != size2)
return -1;
if(size1 == 0 || size2 == 0)
return -1;
//memcmp() wannabe
char *p1 = value1;
char *p2 = value2;
int sz = size1;
do {
if(*p1 != *p2)
break;
else
p1++, p2++;
}
while(--sz != 0);
if(sz == 0)
return 0;
return -1;
}
int main() {
struct s1 v1;
struct s1 v2;
v1.a = 1;
v1.b = 2;
v1.c = 'a';
v2.a = 1;
v2.b = 2;
v2.c = 'a';
int res = memcmpwannabe((void*) &v1, (void*) &v2, sizeof(s1), sizeof(s1));
printf("Res: %d\n", res);
}
The structures are identical, but it will return -1 anyway.
After some debugging i found that the 3 padding bytes after the char variable
are filled with random data, not with zeroes as i was expecting.
Knowing this and the fact that i want to keep it as generic as possible (so using void* as arguments), can somebody point me to an alternative byte to byte comparison?
(Before somebody asks, i'm writing a custom memcmp() because in some implementations it will continue after a difference
Knowing this and the fact that i want to keep it as generic as
possible (so using void* as arguments)
If you only take void * pointers, by definition you know nothing about the internal organization of the objects. Which means you know nothing about the meaning of the bytes (which are in "real" members and which are in hidden, padding-only members).
I would say this is not something that you can easily do with standard C.
I have a following function process calling a routine dataFileBuffer which takes a pointer to a pointer and does a memcpy on the dereferenced pointer location.
int dataFileBuffer(uint8_t *index, char **tempBuf,int size)
{
if(index != stop_address)) /*stop_address is a fixed pointer to the end buffer*/
{
if(*tempBuf)
{
if(index + size < stop_address)
memcpy(*tempBuf,index,size);
else
{
size = stop_address-index-1;
memcpy(*tempBuf,index,size);
}
}
else
size = 0;
}
else
size = 0;
return size;
}
int process()
{
char *readBuf=NULL;
char *tBuf = (char *)malloc(MAX_LENGTH);
int readBytes = -1;
uint8_t *index = start_address;
uint8_t *complete = stop_address;
do
{
readBuf = tBuf+(sizeof(char)*40);
readBytes = 0;
readBytes = dataFileBuffer(index,&readBuf,MAX_LENGTH);
if(readBytes > 0)
{
index = index+readBytes;
}
}while(index <= complete);
return readBytes;
}
My process function is intermittently seeing stack corruptions which is making me think that something is wrong with my implementation of copy.
I just wanted to understand if we can pass a pointer to a pointer as an argument and safely memcpy to the dereferenced location in the called function ?
There are several things wrong with the question's code. Apart from some syntax errors, there is notably the function
dataFileBuffer(index, char **tempBuf,int size)
which does not compile for two reasons, there is no type declared for the argument index, and there is no return value declared - note that the function ends with
return size;
and is called like this:
readBytes = dataFileBuffer(index,&readBuf,MAX_LEN);
and my guess is that it should be
int dataFileBuffer(char *index, char **tempBuf, int size)
but I am puzzled why you have reversed the arguments given to dataFileBuffer() for the memcpy().
Next, you have used MAX_LEN, MAX_LENGTH and 40 to define buffers sizes or offsets, but there is no clear definition or checking as to the size of the available buffer index that you copy into - or is that from :-). It is more usual to offer a buffer size than a pointer limit.
You also have
...
readBytes = dataFileBuffer(index,&readBuf,MAX_LEN);
if(readBytes > 0)
{
index = index+readBytes;
}
} while(index <= complete);
which is likely to cause an infinite loop when readBytes == 0, and anyway will copy the same data on subsequent loops.
Sorry I can't offer a proper solution, as it's all a confused mess.
Added after OP comment
In reply to the specific question about deferencing a **pointer, this example succeeds in doing that, by finding the string length.
#include <stdio.h>
#include <string.h>
// return the length of the string
size_t slen(char **tempBuf)
{
return strlen (*tempBuf);
}
int main(void) {
char string[] = "abcde";
char *sptr = string;
printf ("Length of '%s' is %d\n", string, slen (&sptr));
return 0;
}
Program output:
Length of 'abcde' is 5
I am using the itoa() function to convert an int into string, but it is giving an error:
undefined reference to `itoa'
collect2: ld returned 1 exit status
What is the reason? Is there some other way to perform this conversion?
Use snprintf, it is more portable than itoa.
itoa is not part of standard C, nor is it part of standard C++; but, a lot of compilers and associated libraries support it.
Example of sprintf
char* buffer = ... allocate a buffer ...
int value = 4564;
sprintf(buffer, "%d", value);
Example of snprintf
char buffer[10];
int value = 234452;
snprintf(buffer, 10, "%d", value);
Both functions are similar to fprintf, but output is written into an array rather than to a stream. The difference between sprintf and snprintf is that snprintf guarantees no buffer overrun by writing up to a maximum number of characters that can be stored in the buffer.
Use snprintf - it is standard an available in every compilator. Query it for the size needed by calling it with NULL, 0 parameters. Allocate one character more for null at the end.
int length = snprintf( NULL, 0, "%d", x );
char* str = malloc( length + 1 );
snprintf( str, length + 1, "%d", x );
...
free(str);
Before I continue, I must warn you that itoa is NOT an ANSI function — it's not a standard C function. You should use sprintf to convert an int into a string.
itoa takes three arguments.
The first one is the integer to be converted.
The second is a pointer to an array of characters - this is where the string is going to be stored. The program may crash if you pass in a char * variable, so you should pass in a normal sized char array and it will work fine.
The last one is NOT the size of the array, but it's the BASE of your number - base 10 is the one you're most likely to use.
The function returns a pointer to its second argument — where it has stored the converted string.
itoa is a very useful function, which is supported by some compilers - it's a shame it isn't support by all, unlike atoi.
If you still want to use itoa, here is how should you use it. Otherwise, you have another option using sprintf (as long as you want base 8, 10 or 16 output):
char str[5];
printf("15 in binary is %s\n", itoa(15, str, 2));
Better use sprintf(),
char stringNum[20];
int num=100;
sprintf(stringNum,"%d",num);
Usually snprintf() is the way to go:
char str[16]; // could be less but i'm too lazy to look for the actual the max length of an integer
snprintf(str, sizeof(str), "%d", your_integer);
You can make your own itoa, with this function:
void my_utoa(int dataIn, char* bffr, int radix){
int temp_dataIn;
temp_dataIn = dataIn;
int stringLen=1;
while ((int)temp_dataIn/radix != 0){
temp_dataIn = (int)temp_dataIn/radix;
stringLen++;
}
//printf("stringLen = %d\n", stringLen);
temp_dataIn = dataIn;
do{
*(bffr+stringLen-1) = (temp_dataIn%radix)+'0';
temp_dataIn = (int) temp_dataIn / radix;
}while(stringLen--);}
and this is example:
char buffer[33];
int main(){
my_utoa(54321, buffer, 10);
printf(buffer);
printf("\n");
my_utoa(13579, buffer, 10);
printf(buffer);
printf("\n");
}
void itos(int value, char* str, size_t size) {
snprintf(str, size, "%d", value);
}
..works with call by reference. Use it like this e.g.:
int someIntToParse;
char resultingString[length(someIntToParse)];
itos(someIntToParse, resultingString, length(someIntToParse));
now resultingString will hold your C-'string'.
char string[something];
sprintf(string, "%d", 42);
Similar implementation to Ahmad Sirojuddin but slightly different semantics. From a security perspective, any time a function writes into a string buffer, the function should really "know" the size of the buffer and refuse to write past the end of it. I would guess its a part of the reason you can't find itoa anymore.
Also, the following implementation avoids performing the module/devide operation twice.
char *u32todec( uint32_t value,
char *buf,
int size)
{
if(size > 1){
int i=size-1, offset, bytes;
buf[i--]='\0';
do{
buf[i--]=(value % 10)+'0';
value = value/10;
}while((value > 0) && (i>=0));
offset=i+1;
if(offset > 0){
bytes=size-i-1;
for(i=0;i<bytes;i++)
buf[i]=buf[i+offset];
}
return buf;
}else
return NULL;
}
The following code both tests the above code and demonstrates its correctness:
int main(void)
{
uint64_t acc;
uint32_t inc;
char buf[16];
size_t bufsize;
for(acc=0, inc=7; acc<0x100000000; acc+=inc){
printf("%u: ", (uint32_t)acc);
for(bufsize=17; bufsize>0; bufsize/=2){
if(NULL != u32todec((uint32_t)acc, buf, bufsize))
printf("%s ", buf);
}
printf("\n");
if(acc/inc > 9)
inc*=7;
}
return 0;
}
Like Edwin suggested, use snprintf:
#include <stdio.h>
int main(int argc, const char *argv[])
{
int n = 1234;
char buf[10];
snprintf(buf, 10, "%d", n);
printf("%s\n", buf);
return 0;
}
If you really want to use itoa, you need to include the standard library header.
#include <stdlib.h>
I also believe that if you're on Windows (using MSVC), then itoa is actually _itoa.
See http://msdn.microsoft.com/en-us/library/yakksftt(v=VS.100).aspx
Then again, since you're getting a message from collect2, you're likely running GCC on *nix.
see this example
#include <stdlib.h> // for itoa() call
#include <stdio.h>
int main() {
int num = 145;
char buf[5];
// convert 123 to string [buf]
itoa(num, buf, 10);
// print our string
printf("%s\n", buf);
return 0;
}
see this link having other examples.
itoa() function is not defined in ANSI-C, so not implemented by default for some platforms (Reference Link).
s(n)printf() functions are easiest replacement of itoa(). However itoa (integer to ascii) function can be used as a better overall solution of integer to ascii conversion problem.
itoa() is also better than s(n)printf() as performance depending on the implementation. A reduced itoa (support only 10 radix) implementation as an example: Reference Link
Another complete itoa() implementation is below (Reference Link):
#include <stdbool.h>
#include <string.h>
// A utility function to reverse a string
char *reverse(char *str)
{
char *p1, *p2;
if (! str || ! *str)
return str;
for (p1 = str, p2 = str + strlen(str) - 1; p2 > p1; ++p1, --p2)
{
*p1 ^= *p2;
*p2 ^= *p1;
*p1 ^= *p2;
}
return str;
}
// Implementation of itoa()
char* itoa(int num, char* str, int base)
{
int i = 0;
bool isNegative = false;
/* Handle 0 explicitely, otherwise empty string is printed for 0 */
if (num == 0)
{
str[i++] = '0';
str[i] = '\0';
return str;
}
// In standard itoa(), negative numbers are handled only with
// base 10. Otherwise numbers are considered unsigned.
if (num < 0 && base == 10)
{
isNegative = true;
num = -num;
}
// Process individual digits
while (num != 0)
{
int rem = num % base;
str[i++] = (rem > 9)? (rem-10) + 'a' : rem + '0';
num = num/base;
}
// If number is negative, append '-'
if (isNegative)
str[i++] = '-';
str[i] = '\0'; // Append string terminator
// Reverse the string
reverse(str);
return str;
}
Another complete itoa() implementatiton: Reference Link
An itoa() usage example below (Reference Link):
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main()
{
int a=54325;
char buffer[20];
itoa(a,buffer,2); // here 2 means binary
printf("Binary value = %s\n", buffer);
itoa(a,buffer,10); // here 10 means decimal
printf("Decimal value = %s\n", buffer);
itoa(a,buffer,16); // here 16 means Hexadecimal
printf("Hexadecimal value = %s\n", buffer);
return 0;
}
if(InNumber == 0)
{
return TEXT("0");
}
const int32 CharsBufferSize = 64; // enought for int128 type
TCHAR ResultChars[CharsBufferSize];
int32 Number = InNumber;
// Defines Decreasing/Ascending ten-Digits to determine each digit in negative and positive numbers.
const TCHAR* DigitalChars = TEXT("9876543210123456789");
constexpr int32 ZeroCharIndex = 9; // Position of the ZERO character from the DigitalChars.
constexpr int32 Base = 10; // base system of the number.
// Convert each digit of the number to a digital char from the top down.
int32 CharIndex = CharsBufferSize - 1;
for(; Number != 0 && CharIndex > INDEX_NONE; --CharIndex)
{
const int32 CharToInsert = ZeroCharIndex + (Number % Base);
ResultChars[CharIndex] = DigitalChars[CharToInsert];
Number /= Base;
}
// Insert sign if is negative number to left of the digital chars.
if(InNumber < 0 && CharIndex > INDEX_NONE)
{
ResultChars[CharIndex] = L'-';
}
else
{
// return to the first digital char if is unsigned number.
++CharIndex;
}
// Get number of the converted chars and construct string to return.
const int32 ResultSize = CharsBufferSize - CharIndex;
return TString{&ResultChars[CharIndex], ResultSize};
Basically I'm writing a printf function for an dedicated system so I want to pass an optional number of arguments without using VA_ARGS macros. I knocked up a simple example and this block of code works:
#include <stdio.h>
void func(int i, ...);
int main(int argc, char *argv);
int main(int argc, char *argv) {
unsigned long long f = 6799000015ULL;
unsigned long long *g;
//g points to f
g = &f;
printf("natural: %llu in hex: %llX address: %x\n", *g, *g, g);
//put pointer onto stack
func(6, g, g);
return 0;
}
void func(int i, ...) {
unsigned long long *f;
//pop value off
f = *(&i + 1);
printf("address: %x natural: %llu in hex: %llX\n", f, *f, *f);
}
However the larger example I'm trying to transfer this to doesn't work.
(in the main function):
unsigned long long f = 6799000015ULL;
unsigned long long *g;
g = &f;
kprintf("ull test: 1=%U 2=%X 3=%x 4= 5=\n", g, g, g);
(my dodgy printf function that I'm having trouble with. It maybe worth pointing out
this code DOES work with ints, char strings or anyother % flags which are passed by
value and not pointer. The only difference between what did work and the unsigned
long longs is one is bigger, so I pass by value instead to ensure I don't increment
the &format+ args part wrongly. Does that make sense?)
void kprintf(char *format, ...)
{
char buffer[KPRINTF_BUFFER_SIZE];
int bpos = 0; /* position to write to in buffer */
int fpos = 0; /* position of char to print in format string */
char ch; /* current character being processed*/
/*
* We have a variable number of paramters so we
* have to increment from the position of the format
* argument.
*/
int arg_offset = 1;
/*
* Think this through Phill. &format = address of format on stack.
* &(format + 1) = address of argument after format on stack.
* void *p = &(format + arg_offset);
* kprintf("xxx %i %s", 32, "hello");
* memory would look like = [ 3, 32, 5, "xxx", 32, "hello" ]
* get to 32 via p = &(format + 1); (int)p (because the int is copied, not a pointer)
* get to hello via p = &(format + 2); (char*)p;
*/
void *arg;
unsigned long long *llu;
arg = (void*) (&format + arg_offset);
llu = (unsigned long long*) *(&format + arg_offset);
while (1)
{
ch = format[fpos++];
if (ch == '\0')
break;
if (ch != '%')
buffer[bpos++] = ch;
else
{
ch = format[fpos++];
if (ch == 's')
bpos += strcpy(&buffer[bpos], KPRINTF_BUFFER_SIZE - bpos, (char*)arg);
else if (ch == '%')
buffer[bpos++] = '%';
else if (ch == 'i')
bpos += int_to_str(&buffer[bpos], KPRINTF_BUFFER_SIZE - bpos, *((int*)arg));
else if (ch == 'x')
bpos += int_to_hex_str(&buffer[bpos], KPRINTF_BUFFER_SIZE - bpos, *((int*)arg));
else if (ch == 'o')
bpos += int_to_oct_str(&buffer[bpos], KPRINTF_BUFFER_SIZE - bpos, *((int*)arg));
else if (ch == 'X') {
//arg is expected to be a pointer we need to further dereference.
bpos += unsigned_long_long_to_hex(&buffer[bpos], KPRINTF_BUFFER_SIZE - bpos, *llu);
} else if (ch == 'U') {
bpos += unsigned_long_long_to_str(&buffer[bpos], KPRINTF_BUFFER_SIZE - bpos, *llu);
} else
{
puts("invalid char ");
putch(ch);
puts(" passed to kprintf\n");
}
arg_offset++;
arg = (void *)(&format + arg_offset);
llu = (unsigned long long*) *(&format + arg_offset);
}
}
buffer[bpos] = '\0';
puts(buffer);
}
(and the unsigned long long functions it goes on to call):
int unsigned_long_long_to_hex(char *buffer, int max_size, unsigned long long number)
{
return ull_number_to_str(buffer, max_size, number, BASE_HEX);
}
int unsigned_long_long_to_str(char *buffer, int max_size, unsigned long long number) {
return ull_number_to_str(buffer, max_size, number, BASE_DECIMAL);
}
int ull_number_to_str(char *buffer, int max_size, unsigned long long number, int base) {
int bufpos = 0;
unsigned int lo_byte = (unsigned int) number;
unsigned int hi_byte = (unsigned int) (number >> 32);
bufpos = number_to_str(buffer, max_size, lo_byte, base);
bufpos += number_to_str(buffer + bufpos, max_size, hi_byte, base);
return bufpos;
}
#define NUMERIC_BUFF_SIZE (11 * (ADDRESS_SIZE / 32))
int number_to_str(char *buffer, int max_size, int number, int base)
{
char *char_map = "0123456789ABCDEF";
int remain = 0;
char buff_stack[NUMERIC_BUFF_SIZE];
int stk_pnt = 0;
int bpos = 0;
/* with this method of parsing, the digits come out backwards */
do
{
if (stk_pnt > NUMERIC_BUFF_SIZE)
{
puts("Number has too many digits to be printed. Increasse NUMBERIC_BUFF_SIZE\n");
return 0;
}
remain = number % base;
number = number / base;
buff_stack[stk_pnt++] = char_map[remain];
} while (number > 0);
/* before writing...ensure we have enough room */
if (stk_pnt > max_size)
{
//error. do something?
puts("number_to_str passed number with too many digits to go into buffer\n");
//printf("error. stk_pnt > max_size (%d > %d)\n", stk_pnt, max_size);
return 0;
}
/* reorder */
while (stk_pnt > 0)
buffer[bpos++] = buff_stack[--stk_pnt];
return bpos;
}
Sorry guys, I can't see what I've done wrong. I appreciate this is a "wall of code" type scenario but hopefully someone can see what I've done wrong. I appreciate you probably dislike not using VA_ARGS but I don't understand why this technique shouldn't just work? And also, I'm linking with -nostdlib too. If someone can help I'd really appreciate it. Also, this isn't meant to be production quality code so if I lack some C fundamentals feel free to be constructive about it :-)
It's a bad idea to code this way. Use stdarg.h.
On the off chance (I presume this based on the name kprintf) that you're working on a hobby kernel or embedded project and looking to avoid using standard libraries, I recommend at least writing your own (architecture and compiler specific) set of stdarg macros that conform to the well-known interfaces and code against that. That way your code doesn't look like such a WTF by dereferencing past the address of the last argument.
You can make a va_list type that stores the last-known address, and your va_arg macro could appropriately align the sizeof of the type it's passed and advance the pointer accordingly. For most conventions I have worked on for x86, every type is promoted to 32 bits...
You have to read on the calling conventions for your platform, i.e. how on your target processor/OS function arguments are passed, and how registers are saved. Not all parameters are passed on stack. Depending on number of parameters and their types, many complex situations can arise.
I should add: if you want to manipulate the stack by hand as you are doing above, you need to do it in assembler, not in C. The C language follows a defined standard, and what you are doing above it not legal code (i.e., its meaning is not well-defined). As such, the compiler is allowed to do anything it wants with it, such as optimize it in weird ways unsuitable to your needs.