7 ms·
Intermediate C Programming Notes
- apaprocki 12y agoSolid resource, but definitely a little dated (i.e. no C99). The 'Returning Arrays' part talks about this: char *itoa(int n, char buf[]) { sprintf(buf, "%d", n); return buf; } ... but you'd really want to take a length parameter and call snprintf to prevent buffer overflows. This pre-C99 style C is one of the reasons C has a bad rep wrt memory hazards. edit: Anyone interested in this should probably read through something newer, like http://c.learncodethehardway.org/book/ http://c.learncodethehardway.org/book/
- jabits 12y agoThanks for the link!
- mtdewcmu 12y agoIt's converting an int to decimal, so there's an upper bound on how long the string will need to be. You could argue that the length parameter would be superfluous.
- Someone 12y agoYeah, you can assume everybody will pass a buffer of length at least ten (2^31 has 10 digits). Oops, make that eleven, for the zero terminator. Oops, make that twelve, for the sign. Oops, int can be 64 bits, better make that twenty-one. Oops, sprintf honors locale. There may be thousands separators in there. Make it twenty-seven (or twenty-eight? I lost count. Also, looking at lconv, decimal points and thousands separators need not be at every third digit and may be more than one character long) Yeah, I am sure that length parameter is superfluous.
- mgraczyk 12y agoI did not like the "Returning Arrays" section. There are perfectly reasonable ways of returning small arrays in C. typedef struct { char c_str[25]; } number_string_t; number_string_t itoa(int i) { number_string_t retval = {0}; int written = snprintf(&retval.c_str[0], sizeof(retval.c_str), "%d", i); // Error checking..... return retval; }
- apaprocki 12y agoThere are performance reasons why you may not want to do so, though. GCC has a warning option (-Waggregate-return) to detect this.
- DSMan195276 12y agoI'm curious on the performance issues. IIRC, common x86 C calling convention does returning structs by requiring the caller to allocate memory for the struct (on the stack or otherwise) and then pass a pointer. If that happens then it becomes no different then if you had simply allocated the array on the stack and then passed a pointer to it to the function. Are the performance issues in the case the architecture does aggregate type returning differently?
- fit2rule 12y ago>I'm curious on the performance issues. Whether you can put your return value in a register, or whether you need to housekeep the stack. That's a performance difference.
- apaprocki 12y agoIf you know your target and know your compiler very well (and make sure that nothing changes the assumptions made over time) then I suppose you can take advantage of that. C++ also has return value optimization but there is no way for the author of the code to know if the various platforms/compilers will actually optimize it, so blindly relying on it can result in poor performance. And yes, different platforms handle it differently. IIRC SysV PowerPC only supports up to 64 bits in r3,r4 but AIX POWER forces all returned structures into memory. I think SPARC ABI prior to V9 did not use registers either.
- tpush 12y agoNote that, according to SysV AMD64 calling convention, structs with integer/pointer members up to 128 bits(including padding) are passed in registers. ARM 64 bit is similar in this respect, I believe.
- 12y ago
- DSMan195276 12y agoThis looks like a fairly good read for C programming, but it does seem to be showing it's age and has some (now) misinformation. The Copyright is 1996 to 1999, so from what I've looked at this is all C89, no C99 which is what you'll probably see at this point if you look at some C code. I admit to not having read the entire thing, but I've looked through most of it. A few things that stood out to me: 1. They dance around what a multidimensional array in C actually is, and the line "C does not have true multidimensional arrays." is simply false. I'd let it go, but there's a disconnect from when they go from sections 23 and 23.1 to section 23.2. Section 23.2 is most definitely not a multidimension array. Allocating an actual multidimensional array is actually much simpler, just malloc a block of memory into a pointer to an array: int (*a)[3] = malloc(2 * 3 * sizeof(int)); This allocated array works just like any other multidimensional array in C and can be passed as such, and it's much easier to use then the pointer-to-pointer approach. The pointer-to-pointer approach is the only one shown though, as though it's the only option, even though it's not equivalent. 2. In section 18.1.7, type qualifiers, they say 'const' is fairly rare to see. That may have been true when this was written, but in properly written code today it should be decently common to see, mostly in the case that functions are receiving pointers to data they shouldn't be changing and for string literals that shouldn't be changed. Not using const correctly is a source for lots of annoying compilation warnings and runtime errors. 3. The very end of section 20.1 notes that there is no way to define a variable argument macro. That was true at the time of writing (kinda), but it's not true with C99. 4. In section 18.3.1, the switch statement, everything's perfectly fine except that where it says it's common to use a list of #define's over a switch. It's much better to use an enum type to do so since it can annotate your code a bit better, acts basically the same way, you'll get better compilation warnings from it, and in most cases you can let the compiler assign the numbers for you and thus not risk any conflicts from adding and removing options. For whatever reason, I couldn't find any info on enum's in the entire read, which is a huge red-flag. 5. A lot of their function pointer information involves pointers defined as: int (*p)(); The huge issue with that is that they defined it with an unknown parameter list, and it was intended by the author. There are basically zero times when you actually want to do that, in most cases you should either include the argument types, such as: int (*p)(int); Or put void to indicate no arguments: int (*p)(void);
- 12y ago
- ejr 12y agoThis looks to be very good. One tiny, and I do mean tiny, wish is that the code follows BSD/KNF style. I know that's all down to preference anyway, but it does make things easier to absorb.
- advocaat23 12y agoI think it's always weird how multi-dimensional arrays are implemented in most C programming books. Do you guys really use a pointer-pointer approach to represent them? Because nowadays I always allocate a flat array and just calculate the indices. E.g. struct dmatrix { size_t rows, cols; double *data; } and then a simple double dmatrix_get(struct dmatrix *m, size_t r, size_t c) { return m->data[r * m->cols + c]; } is all you need? In particular I find explicit array sizes in function headers very contrived and I have never used them in serious code. Normally it's just a pointer and a length argument similar to e.g. read/write functions in the standard library.
- silentvoice 12y agoI actually believe this is the preferable approach for performance portability reasons. It's easier to reason about cache /vectorization performance across architectures and compilers when dealing with a single pointer and calculated indices. If you absolutely have to have a pointer to say the first element of a row (resp. column if using column major) you can always do this: double* dmatrix_get_row(struct dmatrix* m, size_t r){ return &m->data[r*m->cols]; } A pointer-to-pointer approach I think is most suitable for an actual array-of-arrays, where the length of each subarray can be different. Then you store an array of pointers to the first element of each subarray, and if the data is contiguous you can use that to calculate lengths as well.