gcc 4.7.2
c89
bstrlib.h from Better String Library
I am maintaining someone's code that looks like this:
FILE *db_fp = NULL;
bstring data = NULL;
db_fp = db_open(DB_FILE, "r");
LOG_CHECK(db_fp == NULL, "Failed to open database: %s", DB_FILE);
data = bread((bNread)fread, db_fp);
LOG_CHECK(data == NULL, "Failed to read from db file: %s", DB_FILE);
db_close(db_fp);
return data;
I am having a little trouble understanding the following line:
data = bread((bNread)fread, db_fp);
What I can guess, is that it is fetching a bstream from the following file pointer and returning a bstring. However, I am wondering about bread and fread.
bread contains 2 arguments, A function pointer (bNread) and the file pointer. But I am not sure I understand how it works.
The declaration for bread() in the Better String Library documentation is:
typedef size_t (*bNread)(void *buff, size_t elsize, size_t nelem, void *parm);
extern bstring bread(bNread readPtr, void *parm);
Therefore, bread() is a function that takes a pointer to a function as an argument. The bNread type is used to specify the type of function. The fread() function is close enough to pass muster when cast — the match isn't exact because it expects a FILE * for its fourth argument and a true function of the type pointed at by a bNread expects a void *. The second argument to bread is the value to be passed as the fourth argument to the function pointed to by the bNread.
So, in the code you're maintaining:
data = bread((bNread)fread, db_fp);
The Better String Library function is called with fread() as the I/O function, cast to the correct type to quell an otherwise justified compiler warning about a mismatch in types, plus the file stream that should be used for reading.
The implementation of the bread() function uses the function pointer and the stream pointer whenever it needs to do I/O in order to read a string. That is, as KerrekB also explained, the code
might write something like:
char buffer[256];
size_t nbytes = (*readPtr)(buffer, sizeof(char), sizeof(buffer), parm);
or (equivalently):
size_t nbytes = readPtr(buffer, sizeof(char), sizeof(buffer), parm);
(If you learned C long enough ago, the first was the only way to invoke functions through pointers to functions; since the C89 standard was produced, the second has been available and is probably used more widely these days.) There are a number of tricks that the implementation must worry about, but the basic function call would be somewhat similar. The code could use I/O functions other than fread(), though the interface to the function must be similar.
Pointers to functions are powerful, but are arcane until you've used them.
Here's one possible implementation of bread:
bstring bread(int(*func)(void *, size_t, size_t ,FILE *), FILE * fp)
{
char buf[10];
bstring result;
func(buf, 10, 1, fp);
return result;
}
This doesn't of course reflect what the real function bread is doing, but it should give you an idea how an implementation might use it.
(The real function would of course read a length from fp first, then allocate enough memory for the string, and then read the string, but the details depend on what bstring is and how it works. A real implementation would also check the return value of func!)
Related
Im trying to implement a function with the following prototype.
int get_next_line(const int fd, char **line) .
The first parameter is the file descriptor that will be used to read.
The second parameter is the address of a pointer to a character that will be used
to save the line read from the file descriptor.
The only thing i know about difference of passing reference and value is that referencing allows you modify the content value.
But for the given prototype, we have the address of the first characters, so i don't fully understand the need for the double pointer.
[Edited] : We store one line of chars inside '**line'.
My question is:
Why use **line and not just *line , or why not ?
int get_next_line(int fd, char **line);
This is a usable function. It presumably allocates a character array of tentatively sufficient size, reads a line into it, reallocates and reads the remainder if necessary, and then passes the allocated string back by assigning it to the dereferenced second argument (*line = allocated_string;).
int get_next_line(int fd, char *line);
This is patent nonsense. There is no way to write get_next_line that is remotely as usable as the previous one. It cannot determine the size of the character array passed to it, nor can it reallocate this array. The only way to specify this function to behave somewhat sensibly is to demand that the argument must be an array of some pre-determined size specified elsewhere. This could only be useful in some pretty narrow contexts.
Note that this is not an immediately obvious observation. The C language used to have gets up until 2011. Programmers used to be rather careless back when.
int get_next_line(int fd, char *line, size_t size);
This could be a somewhat useful function. It could work almost like the standard read function, except it wouldn't read past the first end-of-line character. (Or like fgets for Posix fike handles.) Of course users need to deal with the case of the input line being longer than their array. Presumably get_next_line would return the number if scanned characters.
Note const int is useless in function prototypes. All standard functions specify int in such cases.
I'm stuck for sometime in this code:
#include <stdio.h>
const char *fn; int a;
int exists(const char *fname)
{
FILE *file;
if (file = fopen(fname, "r"))
{
fclose(file);
return 1;
}
return 0;
}
main(){
printf("Name:\n");
scanf("%s",&fn);
a=exists(&fn)
if(a==0){
fopen(&fn,"w");
fprintf(fn,"banhdhsjha");
}
}
When I try to run the program, it works until fprintf(fn,"banhdhsjha");, but it crashes here (Windows gives me an error) and the compiler (CodeBlocks) gives me the following notice:
passing argument 1 of 'fopen' from incompatible pointer type.
I am trying to make fprintf write data in the file, but I don't know how to do it. Can you help me?
There are quite a few problems in your code.
You are passing &fn to scanf. This &fn is a pointer to pointer of const char ** type. This does not make sense. Format specifier %s requires a const char * argument, not const char **. You are basically using the pointer fn itself as a target buffer for file name (4 or 8 bytes long, depending on your platform's pointer size). Most likely you type in longer file name than that "buffer" can accommodate. That overrides memory in your program and leads to unpredictable (undefined) behavior.
The proper form is probably scanf("%s", fn), but the problem is that you never allocated memory for the target buffer. You have to make sure that fn points to a char buffer of sufficient size to hold your file name.
Your fopen(&fn,"w") suffers from the same problem (and that is what the compiler is telling you). fopen expects an argument of const char * type and you are passing a const char ** instead. The proper form is fopen(fn,"w"), but again, see 2. Also, fopen return a file handle that you are supposed to store and use later. You are ignoring (discarding) the return value of fopen. That also makes no sense. You need an additional FILE * variable to store the return of fopen. You already know that, judging by what you did in exists function, but somehow you are ignoring that knowledge in your main.
Your fprintf call also makes no sense at all. fprintf requires file handle (of FILE * type) as its first argument. Instead, you are trying to pass it a file name. That's not going to work and that's also going to trigger a diagnostic message from the compiler. You are supposed to store the return value of fopen (as I said in 3) and pass it to fprintf.
Your exists(&fn) call suffers from the same problem as 1 and 3 and produces the same diagnostic message as your fopen call.
Stop trying to write random code. Where did you get the idea to pass file name to fprintf instead of file handle? Read the documentation for each function you are trying to use and act accordingly.
I noticed that I had used a char* variable instead of a FILE* variable in my code when using fopen and fgets, but my code works. I am wondering why this is? A section of my code is as follows.
...
char* filePath = ac->filepath;
char* line = malloc(sizeof(char) * MAX_CHAR_PER_LINE) ;
filePath = fopen(filePath, "r"); // we are assigning the result to a char*, not FILE*
if (filePath == NULL) {
printf ("\n[%s:%d] - error opening file '%s'", __FILE__, __LINE__, filePath);
printf ("\n\n");
exit (1);
}
while ((fgets(line, MAX_CHAR_PER_LINE, filePath) != NULL)) {
...
Both a char* and a FILE* simply store a memory address. C has fairly weak typing (Edit: this was a misunderstanding on my part, see comments below) so it lets you assign pointers without worrying about the type they point to.
fopen returns the address of a FILE object and you store that address somewhere (in your case it is in a char*). When you use the address in fgets it still has the address of the FILE object so everything will work as expected.
Your compiler is very permissive. It should have complained that the FILE* to char* conversion is invalid.
Anyway, assuming the compiler accepts this implicit conversion, the answer is quite simple.
By the C standard, a char* and a void* pointer can hold, without loss, any pointer value.
So, you can convert from SOMETYPE* to char* and then, back from char* to SOMETYPE*, and you get the same pointer you initially had.
Actually, on most systems, all pointers are equivalent and you can freely convert from one to another.
FILE* is a small and opaque value of pointer type. Probably an actual pointer to an internal data structure, but this may be implemented differently (e.g. it might be a UNIX file descriptor converted to a pointer).
The STDIO functions just expect that you use the same opaque FILE* value you got from fopen, in fread/fwrite/fclose.
The FILE* is converted to char* when assigned to filePath.
The char* is converted to FILE* as the first parameter of fgets (again, your compiler should complain), and so, gets back to its initial value.
Suggestion: Use higher levels of errors/warnings in your compiler and fix your code.
strtrns has the following descriptions: desc-1, desc-2
The strtrns() function transforms string and copies it into
result.
Any character that appears in old is replaced with the character in
the same position in new. The new result is returned. .........
This function is a security risk because it is possible to overflow
the newString buffer. If the currentString buffer is larger than the
newString buffer, then an overflow will occur.
And this is its prototype( or "signature"? ":
char * strtrns(const char *string, const char *old, const char *new, char *result);
I've been googling to no avail. I appreciate any tips or advice.
I think you can write your own safe one pretty quickly.
It won't be a direct replacement, as the signature is slightly different, and it will allocate memory that the caller must free, but it can serve mostly the same job.
(I'm also changing the parameter name new, which is a reserved word in C++, and the parameter string which is a very common type in C++. These changes makes the function compatible with C++ code as well)
char* alloc_strtrns(const char *srcstr, const char *oldtxt, const char *newtxt)
{
if (strlen(oldtxt) != strlen(newtxt))
{
return NULL; /* Old and New lengths MUST match */
}
char* result = strdup(srcstr); /* TODO: check for NULL */
/* Caller is responsible for freeing! */
return strtrns(srcstr, oldtxt, newtxt, result);
}
The claim that this function is unsafe is nonsense. In C, whenever you have an interface that takes a pointer to a buffer and fills it with some amount of data, you must have a contract between the caller and callee regarding the buffer size. For some functions where the caller cannot know in advance how much data the callee will write, the most logical interface design (contract) is to have the caller pass the buffer size to the callee and have the callee return an error or truncate the data if the buffer is too small. But for functions like strcpy or in your case strtrns where the number of output bytes is a trivial function (like the identity function) of the number of input bytes, it makes perfectly good sense for the contract to simply be that the output buffer provided by the caller must be at least as large as the input buffer.
Anyone who is not comfortable with strict adherence to interface contracts should not be writing C. There is really no way around this; adding complex bounds-checking interfaces certainly does not solve the problem but just shifts around the nature of the contracts you have to follow.
By the way, strtrns is not a standard function anyway so if you'd prefer a different contract anyway you might be better off writing your own similar function. This would increase portability too.
You don't really have any options in C. You simply have to ensure that the destination buffer is large enough.
Lets say I have a "Passenger" struct, which has a field for a name.
If I do (like the syntax of my book shows):
fread(&passenger, sizeof(Passenger), 1, in_fp);
printf("%s", (*passenger).first_name)
I get a segmentation fault, but if I do:
fread( (char *)passenger, sizeof(Passenger), 1, in_fp);
printf("%s", (*passenger).first_name)
the name read from the file will be printed out.
Looks to me as if 'passenger' is a pointer. If you take &passenger, you are passing the address of the pointer to fread. When you cast it, you are telling fread to treat it as a pointer to a char buffer instead of a pointer to a Passenger.
You probably have a pointer to a Passenger, not a Passenger:
fread(passenger, sizeof(Passenger), 1, in_fp); printf("%s", (*passenger).first_name)
Will most likely do what you want.
In the early days of C language, C had no void * type (it appeared later, was borrowed from C++ actually) and type char * was used as the generic "raw memory" pointer type instead. So, even to this day you might see this habitual rudimentary use of type char * as the generic pointer type in the code, and see other pointer types explicitly converted to and from char * in generic pointer context. I'd guess that the code you quoted does this for that specific reason. (Unless it was you who put this char * cast there. In that case I can only ask "Why?".)
In modern C the first parameter of fread has void * type, meaning that no cast is necessary. This
fread(passenger, sizeof *passenger, 1, in_fp);
whill work just as well.
Your &passenger version makes no sense, since apparently the original intent was to read data into the location passenger points to, not into the passenger pointer object itself.